Add Process.write_[dq]word + rewrite injection.py to use PEB parsing if needed

This commit is contained in:
Clement Rouault
2016-01-28 16:48:44 +01:00
parent 38a2298dee
commit f307f8202a
3 changed files with 184 additions and 122 deletions
+3
View File
@@ -16,6 +16,9 @@ TODO:
- reprise sur erreur
- reprise context modification
- Free all the virtual_alloc
- Real API arround alloc/free memory in WinProcess..
FIXME:
- WMI
- COM initialisation when injected in another process
+103 -78
View File
@@ -8,52 +8,101 @@ import windows.utils as utils
from .native_exec import simple_x86 as x86
from .native_exec import simple_x64 as x64
from windows.native_exec.nativeutils import GetProcAddress64
def get_loadlib_getproc(target):
if windows.current_process.bitness == target.bitness:
LoadLibraryA = utils.get_func_addr('kernel32', 'LoadLibraryA')
GetProcAddress = utils.get_func_addr('kernel32', 'GetProcAddress')
return LoadLibraryA, GetProcAddress
else:
k32 = [x for x in target.peb.modules if x.name == "kernel32.dll"][0]
exp = k32.pe.exports
return exp['LoadLibraryA'], exp['GetProcAddress']
from windows.dbgprint import dbgprint
def load_dll_in_remote_process(target, dll_name):
rpeb = target.peb
if rpeb.Ldr:
# LDR est parcourable, ca va etre deja plus simple..
modules = rpeb.modules
if any(mod.name == dll_name for mod in modules):
# DLL already loaded
dbgprint("DLL already present in DLL", "DLLINJECT")
return True
k32 = [mod for mod in modules if mod.name.lower() == "kernel32.dll"]
if k32:
# We have kernel32 \o/
k32 = k32[0]
try:
load_libraryA = k32.pe.exports["LoadLibraryA"]
except KeyError:
raise ValueError("Kernel32 have no export <LoadLibraryA> (wtf)")
addr = target.virtual_alloc(0x1000)
target.write_memory(addr, dll_name + "\x00")
t = target.create_thread(load_libraryA, addr)
t.wait()
windows.winproxy.VirtualFreeEx(target.handle, addr)
dbgprint("DLL Injected via (LoadLibray)", "DLLINJECT")
return True
# Hardcore mode
# We don't have k32 or PEB->Ldr
# Go inject a GetProcAddress(LoadLib) + LoadLib shellcode :D
if target.bitness == 32:
raise NotImplementedError("Manuel GetProcAddress 32bits")
dll = "KERNEL32.DLL\x00".encode("utf-16-le")
api = "LoadLibraryA\x00"
dll_to_load = dll_name + "\x00"
RemoteManualLoadLibray = x64.MultipleInstr()
code = RemoteManualLoadLibray
code += x64.Mov("R15", "RCX")
code += x64.Mov("RCX", x64.mem("[R15 + 0]"))
code += x64.Mov("RDX", x64.mem("[R15 + 8]"))
code += x64.Call(":FUNC_GETPROCADDRESS64")
code += x64.Mov("RCX", x64.mem("[R15 + 0x10]"))
code += x64.Push("RCX")
code += x64.Push("RCX")
code += x64.Push("RCX")
code += x64.Call("RAX") # LoadLibrary
code += x64.Pop("RCX")
code += x64.Pop("RCX")
code += x64.Pop("RCX")
code += x64.Ret()
RemoteManualLoadLibray += GetProcAddress64
addr = target.virtual_alloc(0x1000)
addr2 = addr + len(dll)
addr3 = addr2 + len(api)
addr4 = addr3 + len(dll_to_load)
target.write_memory(addr, dll)
target.write_memory(addr2, api)
target.write_memory(addr3, dll_to_load)
target.write_qword(addr4, addr)
target.write_qword(addr4 + 8, addr2)
target.write_qword(addr4 + 0x10, addr3)
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
t.wait()
dbgprint("DLL Injected via manual GetProc(LoadLibray)", "DLLINJECT")
return True
# 32 to 32 injection
def generate_python_exec_shellcode_32(target, PYDLL_addr, PyInit, PyRun, PYCODE_ADDR):
LoadLibraryA, GetProcAddress = get_loadlib_getproc(target)
def generate_python_exec_shellcode_32(target, PyInit, PyRun, PYCODE_ADDR):
code = x86.MultipleInstr()
# Load python27.dll
code += x86.Push(PYDLL_addr)
code += x86.Mov('EAX', LoadLibraryA)
code += x86.Call('EAX')
# Get PyInit function into pythondll
code += x86.Push('EAX')
code += x86.Pop('EDI')
code += x86.Push(PyInit)
code += x86.Push('EDI')
code += x86.Mov('EBX', GetProcAddress)
code += x86.Call('EBX')
# Call PyInit
code += x86.Mov('EAX', PyInit)
code += x86.Call('EAX')
# Get PyRun function into pythondll
code += x86.Push(PyRun)
code += x86.Push('EDI')
code += x86.Call('EBX')
# Call PyRun with python code to exec
code += x86.Push(PYCODE_ADDR)
code += x86.Mov('EAX', PyRun)
code += x86.Call('EAX')
code += x86.Pop('EDI')
code += x86.Pop("EDI")
code += x86.Ret()
return code.get_code()
# 64 to 64 injection
def generate_python_exec_shellcode_64(target, PYDLL_addr, PyInit, PyRun, PYCODE_ADDR):
LoadLibraryA, GetProcAddress = get_loadlib_getproc(target)
def generate_python_exec_shellcode_64(target, PyInit, PyRun, PYCODE_ADDR):
Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4)
Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4)
@@ -61,35 +110,16 @@ def generate_python_exec_shellcode_64(target, PYDLL_addr, PyInit, PyRun, PYCODE_
# Do stack alignement
code += x64.Push('RCX')
# Load python27.dll
code += x64.Mov('RCX', PYDLL_addr)
code += x64.Mov('RAX', LoadLibraryA)
code += Reserve_space_for_call
code += x64.Call('RAX')
code += Clean_space_for_call
code += x64.Push('RAX')
code += x64.Pop('RCX')
# Save RCX
code += x64.Push('RCX')
# Align stack
code += x64.Push('RDI')
# Get PyInit function into pythondll
code += Reserve_space_for_call
code += x64.Mov('RDX', PyInit)
code += x64.Mov('RBX', GetProcAddress)
code += x64.Call('RBX')
code += x64.Mov('RAX', PyInit)
# Call PyInit
code += x64.Call('RAX')
code += Clean_space_for_call
# Remove Stack align
code += x64.Pop('RDI')
# Restore pythondll base into rcx
code += x64.Pop('RCX')
# Get PyRun function into pythondll
code += x64.Mov('RDX', PyRun)
code += Reserve_space_for_call
code += x64.Call('RBX')
# Call PyInit with python code to exec
code += x64.Mov('RAX', PyRun)
code += x64.Mov('RCX', PYCODE_ADDR)
# Call PyRun
code += x64.Call('RAX')
code += Clean_space_for_call
# Remove stack alignement
@@ -98,40 +128,32 @@ def generate_python_exec_shellcode_64(target, PYDLL_addr, PyInit, PyRun, PYCODE_
return code.get_code()
def inject_python_command(process, code_injected, PYDLL="python27.dll\x00"):
PyInitT = "Py_Initialize\x00"
def inject_python_command(target, code_injected, PYDLL):
"""Postulate: PYDLL is already loaded in target process"""
PyInit = "Py_Initialize\x00"
Pyrun = "PyRun_SimpleString\x00"
PYCODE = code_injected + "\x00"
remote_addr_base = process.virtual_alloc(len(code_injected) + 0x100)
remote_addr = remote_addr_base
PYDLL_addr = remote_addr
process.write_memory(remote_addr, PYDLL)
remote_addr += len(PYDLL)
pymodule = [mod for mod in target.peb.modules if mod.name == PYDLL][0]
Py_exports = pymodule.pe.exports
PyInit = Py_exports["Py_Initialize"]
Pyrun = Py_exports["PyRun_SimpleString"]
PyInitT_ADDR = remote_addr
process.write_memory(remote_addr, PyInitT)
remote_addr += len(PyInitT)
remote_addr = target.virtual_alloc(len(PYCODE) + 0x100)
target.write_memory(remote_addr, PYCODE)
SHELLCODE_ADDR = remote_addr + len(PYCODE)
Pyrun_ADDR = remote_addr
process.write_memory(remote_addr, Pyrun)
remote_addr += len(Pyrun)
PYCODE_ADDR = remote_addr
process.write_memory(remote_addr, PYCODE)
remote_addr += len(PYCODE)
SHELLCODE_ADDR = remote_addr
if process.bitness == 32:
if target.bitness == 32:
shellcode_generator = generate_python_exec_shellcode_32
else:
shellcode_generator = generate_python_exec_shellcode_64
shellcode = shellcode_generator(process, PYDLL_addr, PyInitT_ADDR, Pyrun_ADDR, PYCODE_ADDR)
process.write_memory(SHELLCODE_ADDR, shellcode)
shellcode = shellcode_generator(target, PyInit, Pyrun, remote_addr)
target.write_memory(SHELLCODE_ADDR, shellcode)
return SHELLCODE_ADDR
def validate_python_dll_presence(process):
def validate_python_dll_presence_on_disk(process):
if windows.current_process.bitness == process.bitness:
return True
if windows.current_process.bitness == 32 and process.bitness == 64:
@@ -146,9 +168,12 @@ def validate_python_dll_presence(process):
raise NotImplementedError("Unknown bitness")
def execute_python_code(process, code):
validate_python_dll_presence(process)
shellcode_remote_addr = inject_python_command(process, code)
return process.create_thread(shellcode_remote_addr, 0)
validate_python_dll_presence_on_disk(process)
load_dll_in_remote_process(process, "python27.dll")
addr = inject_python_command(process, code, "python27.dll")
t = process.create_thread(addr, 0)
return t
retrieve_exc = r"""
import traceback
+78 -44
View File
@@ -5,6 +5,8 @@ import time
import struct
import itertools
from contextlib import contextmanager
import windows
import windows.network
import windows.registry
@@ -321,14 +323,22 @@ class Process(AutoHandle):
"""
return self.exit_code != STILL_ACTIVE
def execute(self, code):
#@contextmanager
#def allocated_memory(self, size):
# addr = self.virtual_alloc(size)
# try:
# yield addr
# finally:
# windows.winproxy.VirtualFreeEx(self.handle, size)
def execute(self, code, parameter=0):
"""Execute some native code in the context of the process
:return: The return value of the native code
:rtype: :class:`int`"""
x = self.virtual_alloc(len(code))
self.write_memory(x, code)
return self.create_thread(x, 0)
return self.create_thread(x, parameter)
def query_memory(self, addr):
"""Query the memory informations about page at ``addr``
@@ -366,6 +376,60 @@ class Process(AutoHandle):
return
addr += x.RegionSize
def read_char(self, addr):
sizeof_char = sizeof(CHAR)
return struct.unpack("<B", self.read_memory(addr, sizeof_char))[0]
def read_dword(self, addr):
sizeof_dword = sizeof(DWORD)
return struct.unpack("<I", self.read_memory(addr, sizeof_dword))[0]
def read_qword(self, addr):
sizeof_qword = sizeof(ULONG64)
return struct.unpack("<Q", self.read_memory(addr, sizeof_qword))[0]
def read_ptr(self, addr):
if self.bitness == 32:
return self.read_dword(addr)
return self.read_qword(addr)
def read_string(self, addr):
res = []
for i in itertools.count():
x = self.read_memory(addr + (i * 0x100), 0x100)
if "\x00" in x:
res.append(x.split("\x00", 1)[0])
break
res.append(x)
return "".join(res)
def read_wstring(self, addr):
res = []
for i in itertools.count():
x = self.read_memory(addr + (i * 0x100), 0x100)
utf16_chars = ["".join(c) for c in zip(*[iter(x)] * 2)]
if "\x00\x00" in utf16_chars:
res.extend(utf16_chars[:utf16_chars.index("\x00\x00")])
break
res.extend(x)
return "".join(res).decode('utf16')
def write_byte(self, addr, byte):
"""write a byte to virtual memory"""
return self.write_memory(addr, struct.pack("<B", byte))
def write_word(self, addr, word):
"""write a word to virtual memory"""
return self.write_memory(addr, struct.pack("<H", word))
def write_dword(self, addr, dword):
"""write a dword to virtual memory"""
return self.write_memory(addr, struct.pack("<I", dword))
def write_qword(self, addr, qword):
"""write a qword to virtual memory"""
return self.write_memory(addr, struct.pack("<Q", qword))
@utils.fixedpropety
def token(self):
"""TODO: DOC"""
@@ -587,44 +651,6 @@ class WinProcess(PROCESSENTRY32, Process):
self.low_read_memory(addr, ctypes.byref(buffer), size)
return buffer[:]
def read_char(self, addr):
sizeof_char = sizeof(CHAR)
return struct.unpack("<B", self.read_memory(addr, sizeof_char))[0]
def read_dword(self, addr):
sizeof_dword = sizeof(DWORD)
return struct.unpack("<I", self.read_memory(addr, sizeof_dword))[0]
def read_qword(self, addr):
sizeof_qword = sizeof(ULONG64)
return struct.unpack("<Q", self.read_memory(addr, sizeof_qword))[0]
def read_ptr(self, addr):
if self.bitness == 32:
return self.read_dword(addr)
return self.read_qword(addr)
def read_string(self, addr):
res = []
for i in itertools.count():
x = self.read_memory(addr + (i * 0x100), 0x100)
if "\x00" in x:
res.append(x.split("\x00", 1)[0])
break
res.append(x)
return "".join(res)
def read_wstring(self, addr):
res = []
for i in itertools.count():
x = self.read_memory(addr + (i * 0x100), 0x100)
utf16_chars = ["".join(c) for c in zip(*[iter(x)] * 2)]
if "\x00\x00" in utf16_chars:
res.extend(utf16_chars[:utf16_chars.index("\x00\x00")])
break
res.extend(x)
return "".join(res).decode('utf16')
# Simple cache test
# real_read = read_memory
#
@@ -668,6 +694,12 @@ class WinProcess(PROCESSENTRY32, Process):
LoadLibrary = utils.get_func_addr('kernel32', 'LoadLibraryA')
return self.create_thread(LoadLibrary, x)
def tst_load_library(self, dll_path, target_addr):
"""Load the library in remote process"""
x = self.virtual_alloc(0x1000)
self.write_memory(x, dll_path)
return self.create_thread(target_addr, x)
def execute_python(self, pycode):
"""Execute Python code into the remote process.
@@ -906,8 +938,9 @@ class RemotePEB(rctypes.RemoteStructure.from_structure(PEB)):
:type: [:class:`LoadedModule`] -- List of loaded modules
"""
res = []
if not self.Ldr.value:
raise ValueError("PEB->Ldr is NULL: cannot walk the module list")
list_entry_ptr = self.Ldr.contents.InMemoryOrderModuleList.Flink.raw_value
current_dll = self.ptr_flink_to_remote_module(list_entry_ptr)
while current_dll.DllBase:
res.append(current_dll)
@@ -938,8 +971,9 @@ if CurrentProcess().bitness == 32:
:type: [:class:`LoadedModule`] -- List of loaded modules
"""
res = []
if not self.Ldr.value:
raise ValueError("PEB->Ldr is NULL: cannot walk the module list")
list_entry_ptr = self.Ldr.contents.InMemoryOrderModuleList.Flink.raw_value
current_dll = self.ptr_flink_to_remote_module(list_entry_ptr)
while current_dll.DllBase:
res.append(current_dll)
@@ -970,9 +1004,9 @@ if CurrentProcess().bitness == 64:
:type: [:class:`LoadedModule`] -- List of loaded modules
"""
res = []
#import pdb;pdb.set_trace()
if not self.Ldr.value:
raise ValueError("PEB->Ldr is NULL: cannot walk the module list")
list_entry_ptr = self.Ldr.contents.InMemoryOrderModuleList.Flink.raw_value
current_dll = self.ptr_flink_to_remote_module(list_entry_ptr)
while current_dll.DllBase:
res.append(current_dll)