Some fixes in code/samples/test

This commit is contained in:
hakril
2018-03-27 20:37:30 +02:00
parent 66852aae37
commit 34c2d94bc2
13 changed files with 180 additions and 73 deletions
+2 -1
View File
@@ -247,6 +247,7 @@ class CtypesGenerator(object):
def generate_into(self, filename):
self.generate()
print("Writing generated code into {0}".format(filename))
with open(filename, "w") as f:
f.write(self.result.getvalue())
@@ -592,7 +593,7 @@ class ModuleGenerator(object):
self.after_ctypes_generator_init(ctypesgen)
finalfilename = "{0}.py".format(self.name)
ctypesgen.generate_into(to_dest(finalfilename))
ctypesgen.generate_into(to_dest(finalfilename)) # Need to handle dest != PythonForWindows
def generate_doc(self, filename):
nodelist = self.nodes
+2
View File
@@ -40,6 +40,8 @@ def full_alpc_server():
windows.utils.print_ctypes_struct(msg.view_attribute, " - VIEW", hexa=True)
view_data = windows.current_process.read_string(msg.view_attribute.ViewBase)
print(" * Reading view content: <{0}>".format(view_data))
# Needed in Win7 - TODO: why is there a different behavior ?
msg.attributes.ValidAttributes -= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
print(" * security_is_valid <{0}>".format(msg.security_is_valid))
print(" * handle_is_valid <{0}>".format(msg.handle_is_valid))
if msg.handle_is_valid:
-3
View File
@@ -21,9 +21,6 @@ class MySink(windows.com.COMImplementation):
IMPLEMENT = gdef.ICallFrameEvents
def OnCall(self, this, frame):
import pdb;pdb.set_trace()
# this = gdef.ICallFrameEvents(this) # TODO: auto-translate this ?
# frame = gdef.ICallFrame(frame)
ifname = gdef.PWSTR()
methodname = gdef.PWSTR()
print("Hello from python sink !")
+3
View File
@@ -41,6 +41,9 @@ RegOpenKeyExA_iat = [n for n in adv_imports if n.name == "RegOpenKeyExA"][0]
# Setup our hook
RegOpenKeyExA_iat.set_hook(open_reg_hook)
### !!!! You must keep the iat_entry alive !!!!
### If the hook is garbage collected while active -> python will crash
# Use python native module _winreg that call 'RegOpenKeyExA'
print("Asking for <MY_SECRET_KEY>")
v = _winreg.OpenKey(1234567, "MY_SECRET_KEY")
+2
View File
@@ -1,4 +1,5 @@
import gc
import time
import pytest
import collections
@@ -35,6 +36,7 @@ def generate_pop_and_exit_fixtures(proc_popers, ids=[], dwCreationFlags=DEFAULT_
def pop_and_exit_process(request):
proc_poper = request.param
proc = proc_poper(dwCreationFlags=dwCreationFlags)
time.sleep(0.1) # Give time to the process to load :)
yield weakref.proxy(proc) # provide the fixture value
try:
proc.exit(0)
+1
View File
@@ -71,6 +71,7 @@ def alpc_view_test_server():
assert msg.type & 0xfff == gdef.LPC_REQUEST
assert msg.view_is_valid
view_data = windows.current_process.read_memory(msg.view_attribute.ViewBase, len(CLIENT_VIEW_DATA))
msg.attributes.ValidAttributes -= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
assert view_data == CLIENT_VIEW_DATA
assert msg.data == CLIENT_VIEW_MESSAGE
msg.data = SERVER_MESSAGE
+8 -6
View File
@@ -19,6 +19,8 @@ def dumped_apisetmap_base_and_version(request):
ctypes_data = ctypes.c_buffer(data)
yield ctypes.addressof(ctypes_data), version
KNOWN_APISETMAP_PREFIX = ["api-", "ext-", "MS-Win-"]
def verify_apisetmap_parsing(apisetmap_base, version=None):
if version is not None:
assert windows.current_process.read_dword(apisetmap_base) == version
@@ -29,12 +31,12 @@ def verify_apisetmap_parsing(apisetmap_base, version=None):
# Verify that at least one entry resolve to kernel32.dll
# This ensure that the ApiSetMap parsing works at least a little
assert "kernel32.dll" in apisetmap_dict.values()
assert all(dll.startswith("api-") or dll.startswith("ext-") for dll in apisetmap_dict)
# This key was found in all current tested version by hand
# Might need to change that if I add another APISET dump
# But as it was in Windows7 to Windows10 there are big chance it will be in others APISET.
# We do as MS code and ignore everything after the last '-'
assert 'api-ms-win-core-com-l2-1-' in apisetmap_dict
assert all(any(dll.startswith(pref) for pref in KNOWN_APISETMAP_PREFIX) for dll in apisetmap_dict)
# This first key was found in most of the tested version by hand
# MS-Win found on: 6.1.7600 (Win7)
assert 'api-ms-win-core-com-l2-1-' in apisetmap_dict or "MS-Win-Core-ProcessThreads-L1-1-" in apisetmap_dict
def test_apisetmap_parsing_current_process():
return verify_apisetmap_parsing(windows.current_process.peb.ApiSetMap)
+14 -1
View File
@@ -1,6 +1,7 @@
import pytest
import os
import sys
import time
import struct
import textwrap
@@ -23,7 +24,8 @@ class TestCurrentProcessWithCheckGarbage(object):
return windows.current_process.peb
def test_get_current_process_modules(self):
assert "python" in windows.current_process.peb.modules[0].name
# Use sys.executable because executable can be a PyInstaller exe
assert os.path.basename(sys.executable) in windows.current_process.peb.modules[0].name
def test_get_current_process_exe(self):
exe = windows.current_process.peb.exe
@@ -151,7 +153,16 @@ class TestProcessWithCheckGarbage(object):
# Python execution
def _skip_if_injection_dll_not_found(self, target):
if windows.current_process.bitness == target.bitness:
return # Should never fail if we have the same bitness
try:
windows.injection.validate_python_dll_presence_on_disk(target)
except IOError as e:
pytest.skip("Python DLL to inject not installed")
def test_execute_python(self, proc32_64):
self._skip_if_injection_dll_not_found(proc32_64)
with proc32_64.allocated_memory(0x1000) as addr:
proc32_64.execute_python('import ctypes; ctypes.c_uint.from_address({0}).value = 0x42424242'.format(addr))
dword = proc32_64.read_dword(addr)
@@ -159,6 +170,7 @@ class TestProcessWithCheckGarbage(object):
def test_execute_python_suspended(self, proc32_64_suspended):
self._skip_if_injection_dll_not_found(proc32_64_suspended)
proc = proc32_64_suspended
with proc.allocated_memory(0x1000) as addr:
proc.execute_python('import ctypes; ctypes.c_uint.from_address({0}).value = 0x42424242'.format(addr))
@@ -217,6 +229,7 @@ class TestProcessWithCheckGarbage(object):
assert exe.bitness == exe_by_module.bitness
def test_execute_python_raises(self, proc32_64):
self._skip_if_injection_dll_not_found(proc32_64)
res = proc32_64.execute_python("import time;time.sleep(0.1); 2")
assert res == True
with pytest.raises(windows.injection.RemotePythonError) as ar:
+53 -10
View File
@@ -16,8 +16,17 @@ from windows.dbgprint import dbgprint
class InjectionFailedError(WindowsError):
pass
def get_kernel32_dll_name():
# Our injected shellcode search for 'kernel32.dll' with a strcmp
# The BaseDllName of k32 might be 'KERNEL32.DLL' or 'kernel32.dll' on different system32
# We base the name on our own loaded kernel32
k32 = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"]
assert len(k32) == 1
k32name = k32[0].BaseDllName.str
return (k32name + "\x00").encode("utf-16-le")
def perform_manual_getproc_loadlib_32(target, dll_name):
dll = "KERNEL32.DLL\x00".encode("utf-16-le")
dll = get_kernel32_dll_name()
api = "LoadLibraryA\x00"
dll_to_load = dll_name + "\x00"
@@ -54,7 +63,7 @@ def perform_manual_getproc_loadlib_32(target, dll_name):
return True
def perform_manual_getproc_loadlib_64(target, dll_name):
dll = "KERNEL32.DLL\x00".encode("utf-16-le")
dll = get_kernel32_dll_name()
api = "LoadLibraryA\x00"
dll_to_load = dll_name + "\x00"
@@ -94,6 +103,19 @@ def perform_manual_getproc_loadlib_64(target, dll_name):
raise InjectionFailedError("Injection of <{0}> failed".format(dll_name))
return True
def generate_simple_LoadLibraryW_64(load_libraryW, remote_store):
code = RemoteLoadLibrayStub = x64.MultipleInstr()
# code += x64.Int3()
code += x64.Mov("RAX", load_libraryW)
code += (x64.Push("RDI") * 5) # Prepare stack
code += x64.Call("RAX")
code += (x64.Pop("RDI") * 5) # Clean stack
code += x64.Mov(x64.deref(remote_store), "RAX")
code += x64.Ret()
return RemoteLoadLibrayStub.get_code()
def perform_manual_getproc_loadlib(target, *args, **kwargs):
if target.bitness == 32:
return perform_manual_getproc_loadlib_32(target, *args, **kwargs)
@@ -101,6 +123,8 @@ def perform_manual_getproc_loadlib(target, *args, **kwargs):
def load_dll_in_remote_process(target, dll_name):
# if target.bitness == 64:
# import pdb;pdb.set_trace()
rpeb = target.peb
if rpeb.Ldr:
# LDR est parcourable, ca va etre deja plus simple..
@@ -119,14 +143,33 @@ def load_dll_in_remote_process(target, dll_name):
raise ValueError("Kernel32 have no export <LoadLibraryA> (wtf)")
with target.allocated_memory(0x1000) as addr:
target.write_memory(addr, (dll_name + "\x00").encode('utf-16le'))
t = target.create_thread(load_libraryW, addr)
t.wait()
if not t.exit_code:
raise InjectionFailedError(u"Injection of <{0}> failed".format(dll_name))
if target.bitness == 32:
target.write_memory(addr, (dll_name + "\x00").encode('utf-16le'))
t = target.create_thread(load_libraryW, addr)
t.wait()
module_baseaddr = t.exit_code
else:
# For 64b target we need a special stub as the return value of
# load_libraryW does not fit in t.exit_code (DWORD)
retval_addr = addr
target.write_ptr(retval_addr, 0)
addr += ctypes.sizeof(ctypes.c_ulonglong)
full_dll_name = (dll_name + "\x00").encode('utf-16le')
target.write_memory(addr, full_dll_name)
param_addr = addr
addr += len(full_dll_name)
shellcode_addr = addr
shellcode = generate_simple_LoadLibraryW_64(load_libraryW, retval_addr)
target.write_memory(shellcode_addr, shellcode)
t = target.create_thread(shellcode_addr, param_addr)
t.wait()
module_baseaddr = target.read_ptr(retval_addr)
if not module_baseaddr:
raise InjectionFailedError(u"Injection of <{0}> failed".format(dll_name))
dbgprint("DLL Injected via LoadLibray", "DLLINJECT")
# Cannot return the full return value of load_libraryW in 64b target.. (exit_code is a DWORD)
return t.exit_code
return module_baseaddr
# Hardcore mode
# We don't have k32 or PEB->Ldr
# Go inject a GetProcAddress(LoadLib) + LoadLib shellcode :D
@@ -287,11 +330,11 @@ def validate_python_dll_presence_on_disk(process):
if windows.current_process.bitness == 32 and process.bitness == 64:
with windows.utils.DisableWow64FsRedirection():
if not os.path.exists(r"C:\Windows\system32\python27.dll"):
raise ValueError("Could not find Python DLL to inject")
raise IOError("Could not find Python DLL to inject")
return True
if windows.current_process.bitness == 64 and process.bitness == 32:
if not os.path.exists(r"C:\Windows\SysWOW64\python27.dll"):
raise ValueError("Could not find Python DLL to inject")
raise IOError("Could not find Python DLL to inject")
return True
raise NotImplementedError("Unknown bitness")
-3
View File
@@ -1,6 +1,5 @@
import collections
import struct
from windows.dbgprint import dbgprint
DEBUG = False
@@ -740,7 +739,6 @@ class Instruction(object):
default_rex = BitArray.from_int(8, 0x40)
def __init__(self, *initial_args):
dbgprint("Assembling {0}{1}".format(type(self).__name__, initial_args), "X64")
for type_encoding in self.encoding:
args = list(initial_args)
res = []
@@ -759,7 +757,6 @@ class Instruction(object):
else: # if no break
if args: # if still args: fail
continue
dbgprint("Valid encoding found: REX={0:#x}".format(ord(full_rex.dump())), "X64")
self.prefix = prefix
self.value = sum(res, BitArray(0, ""))
if str(full_rex.dump()) != "\x40":
+60 -2
View File
@@ -1,7 +1,11 @@
"""utils fonctions non windows-related"""
import sys
import ctypes
import _ctypes
from windows.generated_def import Flag, LPCSTR, LPWSTR
from windows.generated_def import Flag, LPCSTR, LPWSTR, INFINITE
from windows.dbgprint import dbgprint
from windows import winproxy
def buffer(size): # Test
@@ -24,6 +28,26 @@ def buffer(size): # Test
return ImprovedCtypesBufferImpl()
def wbuffer(size): # Test
buf = ctypes.create_string_buffer(size)
buf.size = size
buf.address = ctypes.addressof(buf)
class ImprovedCtypesBufferImpl(ctypes.Array):
_length_ = size
_type_ = ctypes.c_wchar
def lol(self):
return "lol"
def as_string(self):
return ctypes.cast(self, LPCSTR).value
def as_wstring(self):
return ctypes.cast(self, LPWSTR).value
return ImprovedCtypesBufferImpl()
def fixedpropety(f):
cache_name = "_" + f.__name__
@@ -75,4 +99,38 @@ def print_ctypes_struct(struct, name="", ident=0, hexa=False):
def sprint(struct, name="struct", hexa=True):
"""Print recursively the content of a :mod:`ctypes` structure"""
return print_ctypes_struct(struct, name=name, hexa=hexa)
return print_ctypes_struct(struct, name=name, hexa=hexa)
class AutoHandle(object):
"""An abstract class that allow easy handle creation/destruction/wait"""
# Big bypass to prevent missing reference at programm exit..
_close_function = ctypes.WinDLL("kernel32").CloseHandle
def _get_handle(self):
raise NotImplementedError("{0} is abstract".format(type(self).__name__))
@property
def handle(self):
"""An handle on the object
:type: HANDLE
.. note::
The handle is automaticaly closed when the object is destroyed
"""
if hasattr(self, "_handle"):
return self._handle
self._handle = self._get_handle()
dbgprint("Open handle {0} for {1}".format(hex(self._handle), self), "HANDLE")
return self._handle
def wait(self, timeout=INFINITE):
"""Wait for the object"""
return winproxy.WaitForSingleObject(self.handle, timeout)
def __del__(self):
# sys.path is not None -> check if python shutdown
if hasattr(sys, "path") and sys.path is not None and hasattr(self, "_handle") and self._handle:
# Prevent some bug where dbgprint might be None when __del__ is called in a closing process
dbgprint("Closing Handle {0} for {1}".format(hex(self._handle), self), "HANDLE") if dbgprint is not None else None
self._close_function(self._handle)
+2
View File
@@ -1,3 +1,5 @@
import ctypes
import windows
import windows.generated_def as gdef
+33 -47
View File
@@ -33,44 +33,7 @@ TimeInfo = namedtuple("TimeInfo", ["creation", "exit", "kernel", "user"])
"""Time information about a process"""
class AutoHandle(object):
"""An abstract class that allow easy handle creation/destruction/wait"""
# Big bypass to prevent missing reference at programm close..
_close_function = ctypes.WinDLL("kernel32").CloseHandle
def _get_handle(self):
raise NotImplementedError("{0} is abstract".format(type(self).__name__))
@property
def handle(self):
"""An handle on the object
:type: HANDLE
.. note::
The handle is automaticaly closed when the object is destroyed
"""
if hasattr(self, "_handle"):
return self._handle
self._handle = self._get_handle()
dbgprint("Open handle {0} for {1}".format(hex(self._handle), self), "HANDLE")
#if "DEAD" in str(self):
# print("OPEN FOR THE DEADS")
# import pdb;pdb.set_trace()
return self._handle
def wait(self, timeout=INFINITE):
"""Wait for the object"""
return winproxy.WaitForSingleObject(self.handle, timeout)
def __del__(self):
# sys.path is not None -> check if python shutdown
if hasattr(sys, "path") and sys.path is not None and hasattr(self, "_handle") and self._handle:
# Prevent some bug where dbgprint might be None when __del__ is called in a closing process
dbgprint("Closing Handle {0} for {1}".format(hex(self._handle), self), "HANDLE") if dbgprint is not None else None
self._close_function(self._handle)
class WinThread(AutoHandle):
class WinThread(utils.AutoHandle):
"""Represent a thread """
def __init__(self, tid=None, handle=None, owner_pid=None, owner=None):
@@ -297,7 +260,7 @@ class WinThread(AutoHandle):
# return Token(token_handle.value)
class DeadThread(AutoHandle):
class DeadThread(utils.AutoHandle):
"""An already dead thread (returned only by API returning a new thread if thread die before being returned)"""
def __init__(self, handle, tid=None):
if tid is None:
@@ -325,7 +288,7 @@ class DeadThread(AutoHandle):
return res.value
class Process(AutoHandle):
class Process(utils.AutoHandle):
@utils.fixedpropety
def is_wow_64(self):
"""``True`` if the process is a SysWow64 process (32bit process on 64bits system).
@@ -666,6 +629,12 @@ class Process(AutoHandle):
"""write a qword at ``addr``"""
return self.write_memory(addr, struct.pack("<Q", qword))
def write_ptr(self, addr, value):
"""Write a ``PTR`` at ``addr``"""
if self.bitness == 32:
return self.write_dword(addr, value)
return self.write_qword(addr, value)
@property
def time_info(self):
"""The time information of the process (creation, kernel/user time, exit time)
@@ -739,7 +708,7 @@ class Process(AutoHandle):
# winproxy.OpenProcessToken(self.handle, TOKEN_QUERY, byref(token_handle))
# return Token(token_handle.value)
class CurrentThread(AutoHandle):
class CurrentThread(utils.AutoHandle):
"""The current thread"""
@property #It's not a fixedpropety because executing thread might change
def tid(self):
@@ -874,8 +843,12 @@ class CurrentProcess(Process):
return WinThread._from_handle(handle)
def load_library(self, dll_path):
"""Load the library in current process"""
return winproxy.LoadLibraryA(dll_path)
"""Load the library in current process
:rtype: :class:`LoadedModule`
"""
dllbase = winproxy.LoadLibraryA(dll_path)
return [m for m in self.peb.modules if m.baseaddr == dllbase][0]
def execute(self, code, parameter=0):
"""Execute native code ``code`` in the current thread.
@@ -908,6 +881,8 @@ class CurrentProcess(Process):
winproxy.OpenProcessToken(self.handle, flags, byref(token_handle))
return Token(token_handle.value)
# TODO: use ctypes.string_ad / ctypes.wstring_at for read_string / read_wstring ?
token = property(open_token)
@@ -947,7 +922,7 @@ class WinProcess(Process):
:type: :class:`str`
"""
buffer = ctypes.c_buffer(0x1024)
rsize = winproxy.GetProcessImageFileNameA(self.handle, buffer)
rsize = winproxy.GetProcessImageFileNameA(self.limited_handle, buffer)
# GetProcessImageFileNameA returns the fullpath
return buffer[:rsize].decode().split("\\")[-1]
@@ -1052,8 +1027,12 @@ class WinProcess(Process):
return WinThread._from_handle(winproxy.CreateRemoteThread(hProcess=self.handle, lpStartAddress=addr, lpParameter=param))
def load_library(self, dll_path):
"""Load the library in remote process"""
return windows.injection.load_dll_in_remote_process(self, dll_path)
"""Load the library in remote process
:rtype: :class:`RemoteLoadedModule`
"""
dllbase = windows.injection.load_dll_in_remote_process(self, dll_path)
return [m for m in self.peb.modules if m.baseaddr == dllbase][0]
def execute_python(self, pycode):
"""Execute Python code into the remote process.
@@ -1161,7 +1140,8 @@ SECURITY_MANDATORY_PROTECTED_PROCESS_RID]
know_integrity_level_mapper = gdef.FlagMapper(*KNOW_INTEGRITY_LEVEL)
# Create ProcessToken and Thread Token objects ?
class Token(AutoHandle):
# token.py ?
class Token(utils.AutoHandle):
"""The token of a process"""
def __init__(self, handle):
self._handle = handle
@@ -1215,6 +1195,12 @@ class Token(AutoHandle):
"""The username of the token"""
return self._user_and_computer_name()[0]
def duplicate(self, access_rigth=0, attributes=None, impersonation_level=gdef.SecurityImpersonation, toktype=gdef.TokenPrimary):
newtoken = gdef.HANDLE()
winproxy.DuplicateTokenEx(self.handle, access_rigth, attributes, impersonation_level, toktype, newtoken)
return type(self)(newtoken.value)
def _user_and_computer_name(self):
tok_usr = self.token_user
sid = tok_usr.User.Sid