Skip to content

Commit

Permalink
Merge pull request #1686 from nbargnesi/1680-rewrite-proc-enum
Browse files Browse the repository at this point in the history
partial fix for #1680, rewrite proc enum
  • Loading branch information
doomedraven committed Aug 5, 2023
2 parents 8953380 + 4929dc8 commit 5dcd779
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 44 deletions.
91 changes: 63 additions & 28 deletions analyzer/windows/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
import sys
import timeit
import traceback
from ctypes import POINTER, byref, c_int, c_ulong, c_void_p, cast, create_string_buffer, create_unicode_buffer, sizeof
from ctypes import (
byref,
c_buffer,
c_int,
create_string_buffer,
sizeof,
wintypes,
)
from pathlib import Path
from shutil import copy
from threading import Lock
Expand All @@ -34,7 +41,15 @@
SHUTDOWN_MUTEX,
TERMINATE_EVENT,
)
from lib.common.defines import ADVAPI32, EVENT_MODIFY_STATE, KERNEL32, NTDLL, SYSTEM_PROCESS_INFORMATION
from lib.common.defines import (
ADVAPI32,
EVENT_MODIFY_STATE,
KERNEL32,
PSAPI,
SHELL32,
MAX_PATH,
PROCESS_QUERY_LIMITED_INFORMATION,
)
from lib.common.exceptions import CuckooError, CuckooPackageError
from lib.common.hashing import hash_file
from lib.common.results import upload_to_host
Expand Down Expand Up @@ -82,6 +97,43 @@ def pid_from_service_name(servicename):
return thepid


def pids_from_image_names(suffixlist):
"""Get PIDs for processes whose image name ends with one of the given suffixes.
Matches are not sensitive to casing; both the suffixes and process
image names are normalized prior to comparison.
"""
retpids = []
arr = wintypes.DWORD * 10000 # arbitrary - is this enough?
lpid_process_ptr = arr()
num_bytes = wintypes.DWORD()
image_name = c_buffer(MAX_PATH)
ok = PSAPI.EnumProcesses(byref(lpid_process_ptr), sizeof(lpid_process_ptr), byref(num_bytes))
if not ok:
log.debug("psapi.EnumProcesses failed")
return retpids

suffixlist = tuple([x.lower() for x in suffixlist])
num_processes = int(num_bytes.value / sizeof(wintypes.DWORD))
pids = lpid_process_ptr[:num_processes]

for pid in pids:
h_process = KERNEL32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
if not h_process:
log.debug("kernel.OpenProcess failed for PID: %d", pid)
continue
n = PSAPI.GetProcessImageFileNameA(h_process, image_name, MAX_PATH)
KERNEL32.CloseHandle(h_process)
if not n:
log.debug("psapi.GetProcessImageFileNameA failed for PID: %d", pid)
continue
image_name_pystr = image_name.value.decode().lower()
# e.g., image name: "\device\harddiskvolume4\windows\system32\services.exe"
if image_name_pystr.endswith(suffixlist):
retpids.append(pid)
return retpids


def in_protected_path(fname):
"""Checks file name against some protected names."""
if not fname:
Expand Down Expand Up @@ -186,30 +238,6 @@ def get_pipe_path(self, name):
return f"\\\\.\\PIPE\\{name}"
return f"\\??\\PIPE\\{name}"

def pids_from_process_name_list(self, namelist):
proclist = []
pidlist = []
buf = create_unicode_buffer(1024 * 1024)
p = cast(buf, c_void_p)
retlen = c_ulong(0)
retval = NTDLL.NtQuerySystemInformation(5, buf, 1024 * 1024, byref(retlen))
if retval:
return []
proc = cast(p, POINTER(SYSTEM_PROCESS_INFORMATION)).contents
while proc.NextEntryOffset:
p.value += proc.NextEntryOffset
proc = cast(p, POINTER(SYSTEM_PROCESS_INFORMATION)).contents
# proclist.append((proc.ImageName.Buffer[:proc.ImageName.Length // 2], proc.UniqueProcessId))
proclist.append((proc.ImageName.Buffer, proc.UniqueProcessId))

for proc in proclist:
lowerproc = proc[0].lower()
for name in namelist:
if lowerproc == name:
pidlist.append(proc[1])
break
return pidlist

def prepare(self):
"""Prepare env for analysis."""
global MONITOR_DLL, MONITOR_DLL_64, HIDE_PIDS
Expand Down Expand Up @@ -255,13 +283,15 @@ def prepare(self):
MONITOR_DLL_64 = self.options.get("dll_64")

# get PID for services.exe for monitoring services
svcpid = self.pids_from_process_name_list(["services.exe"])
svcpid = pids_from_image_names(["services.exe"])
if svcpid:
if len(svcpid) > 1:
log.debug("found %d services.exe processes", len(svcpid))
self.SERVICES_PID = svcpid[0]
self.config.services_pid = svcpid[0]
self.CRITICAL_PROCESS_LIST.append(int(svcpid[0]))

HIDE_PIDS = set(self.pids_from_process_name_list(self.files.PROTECTED_NAMES))
HIDE_PIDS = set(pids_from_image_names(self.files.PROTECTED_NAMES))

# Initialize and start the Pipe Servers. This is going to be used for
# communicating with the injected and monitored processes.
Expand Down Expand Up @@ -330,6 +360,11 @@ def run(self):
log.debug("Pipe server name: %s", PIPE)
log.debug("Python path: %s", os.path.dirname(sys.executable))

if SHELL32.IsUserAnAdmin():
log.info("analysis running as an admin")
else:
log.info("analysis running as a normal user")

# If no analysis package was specified at submission, we try to select one automatically.
log.debug("No analysis package specified, trying to detect it automagically")

Expand Down
19 changes: 3 additions & 16 deletions analyzer/windows/lib/common/defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
USER32 = windll.user32
SHELL32 = windll.shell32
PDH = windll.pdh
PSAPI = windll.psapi

BYTE = c_ubyte
USHORT = c_ushort
Expand Down Expand Up @@ -134,6 +135,8 @@
TRUNCATE_EXISTING = 5
CREATE_NO_WINDOW = 0x08000000

MAX_PATH = 260


class STARTUPINFO(Structure):
_fields_ = [
Expand Down Expand Up @@ -253,22 +256,6 @@ class UNICODE_STRING(Structure):
]


class SYSTEM_PROCESS_INFORMATION(Structure):
_fields_ = [
("NextEntryOffset", ULONG),
("NumberOfThreads", ULONG),
("Reserved0", UINT64),
("Reserved1", UINT64),
("Reserved2", UINT64),
("CreateTime", UINT64),
("UserTime", UINT64),
("KernelTime", UINT64),
("ImageName", UNICODE_STRING),
("BasePriority", ULONG),
("UniqueProcessId", PVOID),
]


class SECURITY_DESCRIPTOR(Structure):
_pack_ = 1
_fields_ = [
Expand Down

0 comments on commit 5dcd779

Please sign in to comment.