• Earn real money by being active: Hello Guest, earn real money by simply being active on the forum — post quality content, get reactions, and help the community. Once you reach the minimum credit amount, you’ll be able to withdraw your balance directly. Learn how it works.

Source Code GimmeShelter - highlight opportunities for hiding/running malware from unusual modules

dEEpEst

☣☣ In The Depths ☣☣
Staff member
Administrator
Super Moderator
Hacker
Specter
Crawler
Shadow
Joined
Mar 29, 2018
Messages
13,859
Solutions
4
Reputation
27
Reaction score
45,545
Points
1,813
Credits
55,080
‎7 Years of Service‎
 
56%

Introducing GimmeShelter​

a situational awareness Python script to help you find where to put your beacons​


GimmeShelter is a lightweight Python script which will help you get a good view of what a Windows environment looks like, and highlight opportunities for hiding/running malware from unusual modules, or memory setups.

gimme-shelter.png


Situational Awareness​

Once on a host, a key thing in any red team operation is to become aware of what the “normal” activity on that environment looks like, ideally, very quickly. There is no rocket science here, we are talking about basic things such as the usual browser, which desktop apps connect to the internet, do any of the common apps have weird memory patterns with RWX sections, or odd DLL modules loaded from custom locations ?

Armed with that knowledge it becomes easier to blend in, once you know where to plant a persistency mechanism, or how to execute code, with an much better opsec than if randomly running payloads irrespective of the particularities of the environment.

This is exactly what this script does. It is in Python, which is ubiquitous and rarely suspicious. It is not too intrusive, merely looking around at processes. Do note, though, that it does perform a few API calls (using ctypes) to check memory pages settings in order to find RWX ones.

This is NOT a privesc tool, so it is not checking for processes running as SYSTEM, or for read/write permissions on DLLs (this would mean being much noisier). It is also NOT a memory scanner like
This link is hidden for visitors. Please Log in or register now.
. Should you wish to investigate a specific process or module further, you are encouraged to do so in a lab environment. The SHA256 are displayed so that you can ensure you get the same exact version of the executables or modules that you have identified as being of interest.

What it is looking for​

GimmeShelter will highlight the following potentially interesting properties for the processes currently running under the current user’s context:

  • is the process a dotNET process ? These are known to have more anomalous memory setups than other processes. By default, these are not displayed.
  • does the process have “odd” modules loaded, from unusual directories ? This may open up DLL hijacking/sideloading possibilities.
  • does the process have wininet.dll or winhttp.dll already loaded ? This is obviously a big opsec advantage for a process like that to shelter a beacon, as it will save it from loading those libraries…
  • is the process signed ? Important: the checks performed by the script are not thorough, the signature validity is not checked.
  • does it have Control Flow Guard ? If yes, this has implications if trying to inject and run code into it.
  • does the process have RWX private memory pages ? If so, this type of memory setup may fool detection mechanisms since this is “normal” behavior for that process to produce that type of indicators.
  • does the process have RWX sections ? Similar to the above.
All these indicators should help you identify where and how to set up your loaders and beacons. The SHA256 of the process is displayed so that you can identify the exact version of that executable and ideallyreview it further in a lab, with tools like
This link is hidden for visitors. Please Log in or register now.
which will give you a deeper view of the memory setup of that executable.

Usage​

Code:
.\GimmeShelter.py [options]

options:
  -h, --help      show this help message and exit
  -v, --verbose   Show details on odd modules and RWX sections found

Filtering:
  -d, --dotnet    Display DotNet processes
  -s, --signed    Only show signed processes. WARNING: the validity of signature is NOT checked by this script
  -n, --net-only  Only show processes with winhttp or wininet already loaded

Example Output​

Code:
-------------------
RuntimeBroker.exe       [8224]
  [C:\Windows\System32\RuntimeBroker.exe]
  Sha256: 579dfced8f02a7e1e6e8df10c400117d3127ead7231776a1e467eb507261e920
-------------------
                 (!) Signed
                 (!) has loaded winhttp.dll


-------------------
Code.exe        [13648]
  [C:\Users\mickjagger\AppData\Local\Programs\Microsoft VS Code\Code.exe]
  Sha256: 292e6e6c7d9db5889c170cc71245ced1e8843599673a973841b7d47aa151efb6
-------------------
                 (!) Signed
                 (!) has loaded winhttp.dll


         ==== [ Unusual Modules ] ====

                 c:\users\mickjagger\appdata\local\programs\microsoft vs code\ffmpeg.dll
                         f903f4752f23ad88b08638b8d85c09fc2f1b17084ec655dac89aa559c17387a5

         ==== [ Private memory pages with RWX ] ====

                 ---> 0x18e335201000    49152 bytes
                 ---> 0x18e335221000    24576 bytes
                 ---> 0x18e335243000    102400 bytes
                 ---> 0x7ff6de900000    5505024 bytes

-------------------
firefox.exe     [1248]
  [C:\Program Files\Mozilla Firefox\firefox.exe]
  Sha256: 6835705aad4472891a216ab9ae3a6a7805a27310cdaa0614763b21d352d0a624
-------------------
                 (!) Signed


         ==== [ Unusual Modules ] ====

                 c:\program files\mozilla firefox\lgpllibs.dll
                         1c9c617f13feac3e94a6311eae20997f944da41bebca124002322ac6863d67e8
                 c:\program files\mozilla firefox\xul.dll
                         fa43976fbbbeca97f1116aaf26241daccacf0c29466f17b692d3cf4ee404f1e2
                 c:\program files\mozilla firefox\gkcodecs.dll
                         188f2d44534f978cb28d60e1118e287499adb2f19b0c36929f5badb7c1f94dfd
                 c:\program files\mozilla firefox\nss3.dll
                         8c121892b1bd1a964f194188685ea5d0e057b7d2bf9f9bc328ab5e005518c64d
                 c:\program files\mozilla firefox\freebl3.dll
                         c06ba1a174b47e01cbba84f88d1d66429b6bbda22e389bf579598c24ea2b06d8
                 c:\program files\mozilla firefox\mozglue.dll
                         292e38bd831a5d8948936748f48b1d47cfe49999514e95f5e9b165ef80f1203e
                 c:\program files\mozilla firefox\softokn3.dll
                         3dfa45616bcd5fe594b0dd39acf7f5216f8a3816cee188ef672ff8d44e53a2b8

         ==== [ Private memory pages with RWX ] ====

                 ---> 0x1bfd47b1000     65536 bytes
                 ---> 0x1bfd48b1000     65536 bytes
                 ---> 0x1bfd48d1000     131072 bytes

SOURCE CODE GimmeShelter​

Python:
import sys,os
import argparse
import psutil
import pefile
import ctypes
import hashlib
from ctypes.wintypes import WORD,DWORD,LPVOID
from ctypes import c_void_p


PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ = 0x0010
MEM_COMMIT = 0x1000
MEM_FREE = 0x10000
MEM_RESERVE = 0x2000
MEM_IMAGE = 0x1000000
MEM_MAPPED = 0x40000
MEM_PRIVATE = 0x20000

PAGE_READWRITE = 0x04
PAGE_EXECUTE_READWRITE = 0x40
PAGE_EXECUTE_WRITECOPY = 0x80

class SYSTEM_INFO(ctypes.Structure):
 """https://msdn.microsoft.com/en-us/library/ms724958"""
 class _U(ctypes.Union):
  class _S(ctypes.Structure):
   _fields_ = (('wProcessorArchitecture', WORD),
      ('wReserved', WORD))
  _fields_ = (('dwOemId', DWORD), # obsolete
     ('_s', _S))
  _anonymous_ = ('_s',)
 
 
 if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_ulonglong):
  DWORD_PTR = ctypes.c_ulonglong
 elif ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_ulong):
  DWORD_PTR = ctypes.c_ulong
 
 _fields_ = (('_u', _U),
    ('dwPageSize', DWORD),
    ('lpMinimumApplicationAddress', LPVOID),
    ('lpMaximumApplicationAddress', LPVOID),
    ('dwActiveProcessorMask',   DWORD_PTR),
    ('dwNumberOfProcessors', DWORD),
    ('dwProcessorType',   DWORD),
    ('dwAllocationGranularity', DWORD),
    ('wProcessorLevel', WORD),
    ('wProcessorRevision', WORD))
 _anonymous_ = ('_u',)

class MEMORY_BASIC_INFORMATION(ctypes.Structure):
 """https://msdn.microsoft.com/en-us/library/aa366775"""
 PVOID = LPVOID
 SIZE_T = ctypes.c_size_t
 _fields_ = (('BaseAddress', PVOID),
    ('AllocationBase', PVOID),
    ('AllocationProtect', DWORD),
    ('RegionSize', SIZE_T),
    ('State',   DWORD),
    ('Protect', DWORD),
    ('Type', DWORD))
 
 
def findRWX(p):
  k32 = ctypes.WinDLL('kernel32', use_last_error=True)

  #Get System Info
  LPSYSTEM_INFO = ctypes.POINTER(SYSTEM_INFO) 
  k32.GetSystemInfo.restype = None
  k32.GetSystemInfo.argtypes = (LPSYSTEM_INFO,)
  ReadProcessMemory = k32.ReadProcessMemory
  sysinfo = SYSTEM_INFO()
  k32.GetSystemInfo(ctypes.byref(sysinfo))
  startAddr=sysinfo.lpMinimumApplicationAddress
  currAddr = sysinfo.lpMinimumApplicationAddress
  endAddr = sysinfo.lpMaximumApplicationAddress
  pageSize = sysinfo.dwPageSize

  p = k32.OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, False, p)
  RWXPrivateRanges = []
  RWXImagesRanges = []
  mbi = MEMORY_BASIC_INFORMATION()
  while currAddr < endAddr:
    ret = k32.VirtualQueryEx(p,c_void_p(currAddr),ctypes.byref(mbi), ctypes.sizeof(mbi))
    if(ret == 0):
      print("Error running VirtualQueryEx")
    if mbi.Protect == PAGE_EXECUTE_READWRITE:
       if mbi.Type == MEM_PRIVATE:
          RWXPrivateRanges.append((currAddr,mbi.RegionSize))
       if mbi.Type == MEM_IMAGE:
          RWXImagesRanges.append((currAddr,mbi.RegionSize))
    currAddr = currAddr + mbi.RegionSize

  ret = k32.CloseHandle(p)
  return RWXPrivateRanges,RWXImagesRanges

def isCurrentUser(n):
    currentUser = os.getlogin()
    if (n == currentUser or currentUser == n.split('\\')[1]):
        return True
    return False

def isOddModule(m):
    normalModules = [
        "c:\\windows\\system32",
        "c:\\windows\\fonts",
        "c:\\windows\\globalization\\sorting\\",
        "c:\\windows\\winsxs\\",
        "c:\\windows\\assembly\\",
        "c:\\windows\\microsoft.net\\",
        "c:\\windows\\syswow64\\",
        "c:\\windows\\systemapps\\"
                        ]
    for path in normalModules:
        if m.startswith(path):
            return False
    return True

def hasWininet(m):
   for x in m:
      if x.endswith("wininet.dll"):
         return True
   return False

def isDotNet(m):
   for x in m:
      if x.endswith("clr.dll") or x.endswith("mscoree.dll"):
         return True
   return False


def hasWinhttp(m):
   for x in m:
      if x.endswith("winhttp.dll"):
         return True
   return False

def isDll(m):
    return m.endswith(".dll")

def getHash(filepath):
   sha256 = hashlib.sha256()
   with open(filepath,"rb") as f:
        for b in iter(lambda: f.read(4096),b""):
            sha256.update(b)
        return sha256.hexdigest()
  
def printProcess(d):
    '''
    If there are interesting things to display for that process, print them.
    If not, pass.
    '''
    if not (d["hasWininet"] or d["hasWinhttp"] or len(d["rwxImgs"])>0 or len(d["rwxPriv"])>0 or len(d["oddModules"])>0 or d["rwxSections"]):
       return
    print("-------------------")
    print(f"{d['name']}\t[{d['pid']}]")
    print(f"  [{d['exe']}]")
    hash = getHash(d["exe"])
    print(f"  Sha256: {hash}")
    print("-------------------")
    if(d["hasSignature"]): print("\t\t (!) Signed")
    if(d["noCFG"]): print("\t\t (!) no CF Guard")
    if(d["isDotNet"]): print("\t\t (!) dotNET ")
    if(d["hasWininet"]): print("\t\t (!) has loaded wininet.dll ")
    if(d["hasWinhttp"]): print("\t\t (!) has loaded winhttp.dll ")
    print("")
    if len(d["oddModules"])>0:
        if(not args.verbose):
           print("\t\t (!) Unusual modules found ")
        else:
            print("\n\t ==== [ Unusual Modules ] ====\n")
            for dll in d["oddModules"]:
                print(f"\t\t {dll}")
                print(f"\t\t\t {getHash(dll)}")

    if(d["rwxSections"]):
        if(not args.verbose):
            print("\t\t (!) RWX Sections found")
        else:
            print("\n\t ==== [ RWX Sections ] ====\n")
            print(d["rwxSections"])
    
    if( len(d["rwxImgs"])>0 ):
       if(not args.verbose):
          print("\t\t (!) RWX Sections in Images found")
       else:
        print("\n\t ==== [ Images with RWX ] ====\n")
        for r in d["rwxImgs"]:
            print(f"\t\t ---> 0x{r[0]:08x}\t{r[1]} bytes")

    if( len(d["rwxPriv"])>0 ):
       if(not args.verbose):
          print("\t\t (!) Private RWX Sections in Images found")
       else:
        print("\n\t ==== [ Private memory pages with RWX ] ====\n")
        for r in d["rwxPriv"]:
            print(f"\t\t ---> 0x{r[0]:08x}\t{r[1]} bytes")

    print("\n")

def scanPE(process):
    IMAGE_SCN_MEM_EXECUTE = 0x20000000
    IMAGE_SCN_MEM_READ = 0x40000000
    IMAGE_SCN_MEM_WRITE = 0x80000000
    pe = pefile.PE(process["exe"], fast_load=True)
    process["noCFG"] = not (pe.OPTIONAL_HEADER.DllCharacteristics >> 14) & 0x1

    rwxSections = []
    for s in pe.sections:
       if (s.Characteristics & IMAGE_SCN_MEM_EXECUTE) and (s.Characteristics & IMAGE_SCN_MEM_READ) and (s.Characteristics & IMAGE_SCN_MEM_WRITE):
            rwxSections.append(s.Name)
    process["rwxSections"] = rwxSections

    pe.parse_data_directories( directories=[
    pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']
    ])
        
    process["hasSignature"] = False
    for s in pe.__structures__:
        if s.name == "IMAGE_DIRECTORY_ENTRY_SECURITY":
            process["hasSignature"]=(s.VirtualAddress != 0 and s.Size !=0)
        

    pe.close()

def opts(argv):
    p = argparse.ArgumentParser(prog = argv[0],
                                usage = "%(prog)s [options]")

    p.add_argument('-v','--verbose', action='store_true', help='Show details on odd modules and RWX sections found')

    filter = p.add_argument_group("Filtering")
    filter.add_argument('-d','--dotnet', action='store_true', help='Display DotNet processes')
    filter.add_argument('-s','--signed', action='store_true', help='Only show signed processes. WARNING: the validity of signature is NOT checked by this script')
    filter.add_argument('-n','--net-only', action='store_true', help='Only show processes with winhttp or wininet already loaded')

    return p.parse_args()

def main(argv):
    global args

    print('''
          \t\t\t -*-*- [ GimmeShelter.py ] -*-*- \t\t\t

 Author: RWXstoned\t rwxstoned/at/proton[.]me
---
 Find a shelter for your implants !
 Situational awareness Python script which will help you better blend in when trying to identify how and where to run implants.\n
 Review which DLLs are loaded and where; find out what opportunities might be there for module stomping, DLL hijacking, hosting code in RWX sections, etc...
 If an executable or DLL is of interest to you, make note of its SHA256 and review it in a lab...
          
 NOTES:
 - When an executable is marked "Signed", the check is fairly rudimentary and does not actually check the validity of that signature - which would imply running extra-commands and be less stealthy.
 - This is not a privesc tool. Only processes running under the current user are checked.
---
          ''')
    args = opts(argv)

    currentProcesses = []
    pids = psutil.pids()
    for pid in pids:
        p = psutil.Process(pid)
        try:
            u = p.username()
        except psutil.AccessDenied:
            continue
        if isCurrentUser(u):
            currentProcesses.append(p)

    for p in currentProcesses:
        process = {}
        try:
            process["name"] = p.name()
            process["pid"] = p.pid
            process["exe"] = p.exe()
        except psutil.NoSuchProcess:
           continue
        try:
            mmaps = p.memory_maps()
        except psutil.AccessDenied:
            continue
        all_modules = [m.path.lower() for m in mmaps]
        odd_modules = [m for m in all_modules if isOddModule(m)]
        odd_modules_dll = [m for m in odd_modules if isDll(m)]
        process["isDotNet"] = isDotNet(all_modules)

        if(not args.dotnet and process["isDotNet"]):
           continue

        process["hasWininet"] = hasWininet(all_modules)
        process["hasWinhttp"] = hasWinhttp(all_modules)
        if(args.net_only and not (process["hasWininet"] or process["hasWinhttp"])):
           continue

        process["rwxPriv"],process["rwxImgs"] = findRWX(p.pid)
        process["oddModules"] = odd_modules_dll

        scanPE(process)
        if(args.signed and not process["hasSignature"]):
           continue
        printProcess(process)
    
if __name__ == '__main__':
   main(sys.argv)
 
Back
Top