Added some scripts
This commit is contained in:
309
dump_nemo.py
Normal file
309
dump_nemo.py
Normal file
@@ -0,0 +1,309 @@
|
||||
import ctypes
|
||||
import ctypes.wintypes as wt
|
||||
import struct
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
|
||||
|
||||
# -----------------------------
|
||||
# Windows constants
|
||||
# -----------------------------
|
||||
TH32CS_SNAPPROCESS = 0x00000002
|
||||
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_VM_READ = 0x0010
|
||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
|
||||
MEM_COMMIT = 0x1000
|
||||
|
||||
MEM_IMAGE = 0x1000000
|
||||
MEM_MAPPED = 0x40000
|
||||
MEM_PRIVATE = 0x20000
|
||||
|
||||
PAGE_NOACCESS = 0x01
|
||||
PAGE_READONLY = 0x02
|
||||
PAGE_READWRITE = 0x04
|
||||
PAGE_WRITECOPY = 0x08
|
||||
PAGE_EXECUTE = 0x10
|
||||
PAGE_EXECUTE_READ = 0x20
|
||||
PAGE_EXECUTE_READWRITE = 0x40
|
||||
PAGE_EXECUTE_WRITECOPY = 0x80
|
||||
PAGE_GUARD = 0x100
|
||||
PAGE_NOCACHE = 0x200
|
||||
PAGE_WRITECOMBINE = 0x400
|
||||
|
||||
READABLE_MASK = (
|
||||
PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY |
|
||||
PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY
|
||||
)
|
||||
|
||||
# SeDebugPrivilege (opcjonalnie)
|
||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
||||
TOKEN_ADJUST_PRIVILEGES = 0x20
|
||||
TOKEN_QUERY = 0x8
|
||||
|
||||
# -----------------------------
|
||||
# Structures
|
||||
# -----------------------------
|
||||
class PROCESSENTRY32(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwSize", wt.DWORD),
|
||||
("cntUsage", wt.DWORD),
|
||||
("th32ProcessID", wt.DWORD),
|
||||
("th32DefaultHeapID", ctypes.c_void_p),
|
||||
("th32ModuleID", wt.DWORD),
|
||||
("cntThreads", wt.DWORD),
|
||||
("th32ParentProcessID", wt.DWORD),
|
||||
("pcPriClassBase", wt.LONG),
|
||||
("dwFlags", wt.DWORD),
|
||||
("szExeFile", wt.WCHAR * 260),
|
||||
]
|
||||
|
||||
# MEMORY_BASIC_INFORMATION for VirtualQueryEx:
|
||||
# This layout is correct for 32-bit target from a 64-bit Python too, because VirtualQueryEx
|
||||
# returns pointers sized to the caller, but BaseAddress/RegionSize are pointer-sized.
|
||||
# We'll store them in ctypes.c_size_t and then cast.
|
||||
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("BaseAddress", ctypes.c_void_p),
|
||||
("AllocationBase", ctypes.c_void_p),
|
||||
("AllocationProtect", wt.DWORD),
|
||||
("RegionSize", ctypes.c_size_t),
|
||||
("State", wt.DWORD),
|
||||
("Protect", wt.DWORD),
|
||||
("Type", wt.DWORD),
|
||||
]
|
||||
|
||||
class SYSTEM_INFO(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("wProcessorArchitecture", wt.WORD),
|
||||
("wReserved", wt.WORD),
|
||||
("dwPageSize", wt.DWORD),
|
||||
("lpMinimumApplicationAddress", ctypes.c_void_p),
|
||||
("lpMaximumApplicationAddress", ctypes.c_void_p),
|
||||
("dwActiveProcessorMask", ctypes.c_void_p),
|
||||
("dwNumberOfProcessors", wt.DWORD),
|
||||
("dwProcessorType", wt.DWORD),
|
||||
("dwAllocationGranularity", wt.DWORD),
|
||||
("wProcessorLevel", wt.WORD),
|
||||
("wProcessorRevision", wt.WORD),
|
||||
]
|
||||
|
||||
class LUID(ctypes.Structure):
|
||||
_fields_ = [("LowPart", wt.DWORD), ("HighPart", wt.LONG)]
|
||||
|
||||
class LUID_AND_ATTRIBUTES(ctypes.Structure):
|
||||
_fields_ = [("Luid", LUID), ("Attributes", wt.DWORD)]
|
||||
|
||||
class TOKEN_PRIVILEGES(ctypes.Structure):
|
||||
_fields_ = [("PrivilegeCount", wt.DWORD),
|
||||
("Privileges", LUID_AND_ATTRIBUTES * 1)]
|
||||
|
||||
# -----------------------------
|
||||
# API prototypes
|
||||
# -----------------------------
|
||||
kernel32.CreateToolhelp32Snapshot.argtypes = [wt.DWORD, wt.DWORD]
|
||||
kernel32.CreateToolhelp32Snapshot.restype = wt.HANDLE
|
||||
|
||||
kernel32.Process32FirstW.argtypes = [wt.HANDLE, ctypes.POINTER(PROCESSENTRY32)]
|
||||
kernel32.Process32FirstW.restype = wt.BOOL
|
||||
|
||||
kernel32.Process32NextW.argtypes = [wt.HANDLE, ctypes.POINTER(PROCESSENTRY32)]
|
||||
kernel32.Process32NextW.restype = wt.BOOL
|
||||
|
||||
kernel32.OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]
|
||||
kernel32.OpenProcess.restype = wt.HANDLE
|
||||
|
||||
kernel32.CloseHandle.argtypes = [wt.HANDLE]
|
||||
kernel32.CloseHandle.restype = wt.BOOL
|
||||
|
||||
kernel32.VirtualQueryEx.argtypes = [wt.HANDLE, ctypes.c_void_p, ctypes.POINTER(MEMORY_BASIC_INFORMATION), ctypes.c_size_t]
|
||||
kernel32.VirtualQueryEx.restype = ctypes.c_size_t
|
||||
|
||||
kernel32.ReadProcessMemory.argtypes = [wt.HANDLE, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
|
||||
kernel32.ReadProcessMemory.restype = wt.BOOL
|
||||
|
||||
kernel32.GetSystemInfo.argtypes = [ctypes.POINTER(SYSTEM_INFO)]
|
||||
kernel32.GetSystemInfo.restype = None
|
||||
|
||||
advapi32.OpenProcessToken.argtypes = [wt.HANDLE, wt.DWORD, ctypes.POINTER(wt.HANDLE)]
|
||||
advapi32.OpenProcessToken.restype = wt.BOOL
|
||||
|
||||
advapi32.LookupPrivilegeValueW.argtypes = [wt.LPCWSTR, wt.LPCWSTR, ctypes.POINTER(LUID)]
|
||||
advapi32.LookupPrivilegeValueW.restype = wt.BOOL
|
||||
|
||||
advapi32.AdjustTokenPrivileges.argtypes = [wt.HANDLE, wt.BOOL, ctypes.POINTER(TOKEN_PRIVILEGES),
|
||||
wt.DWORD, ctypes.c_void_p, ctypes.c_void_p]
|
||||
advapi32.AdjustTokenPrivileges.restype = wt.BOOL
|
||||
|
||||
kernel32.GetCurrentProcess.restype = wt.HANDLE
|
||||
|
||||
# -----------------------------
|
||||
# Helpers
|
||||
# -----------------------------
|
||||
def winerr(msg: str):
|
||||
err = ctypes.get_last_error()
|
||||
raise OSError(err, f"{msg} (WinError={err})")
|
||||
|
||||
def enable_debug_privilege():
|
||||
hToken = wt.HANDLE()
|
||||
if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ctypes.byref(hToken)):
|
||||
return False
|
||||
try:
|
||||
luid = LUID()
|
||||
if not advapi32.LookupPrivilegeValueW(None, "SeDebugPrivilege", ctypes.byref(luid)):
|
||||
return False
|
||||
tp = TOKEN_PRIVILEGES(1, (LUID_AND_ATTRIBUTES(luid, SE_PRIVILEGE_ENABLED),))
|
||||
if not advapi32.AdjustTokenPrivileges(hToken, False, ctypes.byref(tp), 0, None, None):
|
||||
return False
|
||||
# AdjustTokenPrivileges can succeed but still not enable; GetLastError==ERROR_NOT_ALL_ASSIGNED
|
||||
if ctypes.get_last_error() == 1300: # ERROR_NOT_ALL_ASSIGNED
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
kernel32.CloseHandle(hToken)
|
||||
|
||||
def find_pid_by_name(exe_name: str) -> int:
|
||||
snap = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if snap == wt.HANDLE(-1).value:
|
||||
winerr("CreateToolhelp32Snapshot failed")
|
||||
|
||||
try:
|
||||
pe = PROCESSENTRY32()
|
||||
pe.dwSize = ctypes.sizeof(PROCESSENTRY32)
|
||||
if not kernel32.Process32FirstW(snap, ctypes.byref(pe)):
|
||||
winerr("Process32FirstW failed")
|
||||
|
||||
exe_name_l = exe_name.lower()
|
||||
while True:
|
||||
if pe.szExeFile.lower() == exe_name_l:
|
||||
return int(pe.th32ProcessID)
|
||||
if not kernel32.Process32NextW(snap, ctypes.byref(pe)):
|
||||
break
|
||||
return 0
|
||||
finally:
|
||||
kernel32.CloseHandle(snap)
|
||||
|
||||
def is_readable_page(prot: int) -> bool:
|
||||
if prot & PAGE_GUARD:
|
||||
return False
|
||||
if prot & PAGE_NOACCESS:
|
||||
return False
|
||||
if prot & READABLE_MASK:
|
||||
return True
|
||||
return False
|
||||
|
||||
@dataclass
|
||||
class RegionIdx:
|
||||
base: int
|
||||
size: int
|
||||
protect: int
|
||||
type: int
|
||||
fileOffset: int
|
||||
|
||||
def dump_process_memory(pid: int, mem_path: str, idx_path: str, chunk_size: int = 1 << 20):
|
||||
access = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
||||
hProc = kernel32.OpenProcess(access, False, pid)
|
||||
if not hProc:
|
||||
# fallback for some processes
|
||||
hProc = kernel32.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
|
||||
if not hProc:
|
||||
winerr("OpenProcess failed")
|
||||
|
||||
index = []
|
||||
try:
|
||||
si = SYSTEM_INFO()
|
||||
kernel32.GetSystemInfo(ctypes.byref(si))
|
||||
p = ctypes.c_void_p(si.lpMinimumApplicationAddress).value
|
||||
max_addr = ctypes.c_void_p(si.lpMaximumApplicationAddress).value
|
||||
|
||||
mbi = MEMORY_BASIC_INFORMATION()
|
||||
|
||||
with open(mem_path, "wb") as fmem:
|
||||
while p < max_addr:
|
||||
got = kernel32.VirtualQueryEx(hProc, ctypes.c_void_p(p), ctypes.byref(mbi), ctypes.sizeof(mbi))
|
||||
if not got:
|
||||
# jump by a page to avoid infinite loop on weird edges
|
||||
p += 0x1000
|
||||
continue
|
||||
|
||||
base = ctypes.c_void_p(mbi.BaseAddress).value
|
||||
rsize = int(mbi.RegionSize)
|
||||
prot = int(mbi.Protect)
|
||||
state = int(mbi.State)
|
||||
rtype = int(mbi.Type)
|
||||
|
||||
if state == MEM_COMMIT and is_readable_page(prot) and rsize > 0:
|
||||
file_off = fmem.tell()
|
||||
written = 0
|
||||
|
||||
cur = base
|
||||
left = rsize
|
||||
|
||||
buf = (ctypes.c_ubyte * min(chunk_size, rsize))()
|
||||
while left > 0:
|
||||
to_read = min(left, chunk_size)
|
||||
bytes_read = ctypes.c_size_t(0)
|
||||
ok = kernel32.ReadProcessMemory(
|
||||
hProc,
|
||||
ctypes.c_void_p(cur),
|
||||
ctypes.byref(buf),
|
||||
to_read,
|
||||
ctypes.byref(bytes_read),
|
||||
)
|
||||
n = int(bytes_read.value)
|
||||
if n > 0:
|
||||
fmem.write(bytes(buf[:n]))
|
||||
written += n
|
||||
cur += n
|
||||
left -= n
|
||||
# jeśli RPM fail albo short read — kończymy region (jak w Twoim C++)
|
||||
if (not ok) or (n < to_read):
|
||||
break
|
||||
|
||||
if written > 0:
|
||||
# Twój format trzyma base jako u32
|
||||
index.append(RegionIdx(
|
||||
base=base & 0xFFFFFFFF,
|
||||
size=written & 0xFFFFFFFF,
|
||||
protect=prot & 0xFFFFFFFF,
|
||||
type=rtype & 0xFFFFFFFF,
|
||||
fileOffset=file_off & 0xFFFFFFFFFFFFFFFF,
|
||||
))
|
||||
|
||||
# następny region
|
||||
p = base + rsize
|
||||
|
||||
# zapis indexu
|
||||
with open(idx_path, "wb") as fidx:
|
||||
hdr = struct.pack("<IIII", 0x584D454D, 1, len(index), 0) # 'MEMX'
|
||||
fidx.write(hdr)
|
||||
for ri in index:
|
||||
fidx.write(struct.pack("<IIIIQ", ri.base, ri.size, ri.protect, ri.type, ri.fileOffset))
|
||||
|
||||
print(f"[+] DONE: wrote {len(index)} regions -> {mem_path} (+ {idx_path})")
|
||||
|
||||
finally:
|
||||
kernel32.CloseHandle(hProc)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python dump_mem.py nemo.exe [dump.mem dump.idx]")
|
||||
sys.exit(1)
|
||||
|
||||
exe = sys.argv[1]
|
||||
mem_path = sys.argv[2] if len(sys.argv) >= 3 else "dump.mem"
|
||||
idx_path = sys.argv[3] if len(sys.argv) >= 4 else "dump.idx"
|
||||
|
||||
dbg = enable_debug_privilege()
|
||||
print(f"[*] SeDebugPrivilege: {'enabled' if dbg else 'not enabled (ok, maybe not needed)'}")
|
||||
|
||||
pid = find_pid_by_name(exe)
|
||||
if not pid:
|
||||
print(f"[-] Process not found: {exe}")
|
||||
sys.exit(2)
|
||||
|
||||
print(f"[*] PID for {exe}: {pid}")
|
||||
dump_process_memory(pid, mem_path, idx_path)
|
||||
Reference in New Issue
Block a user