mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
db1893910c
This update include support to passing command line parameters to unmanaged exe via PEB stomping. This technique is not working with every executable since it depends on which functions are used to pass arguments. Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here: https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping. On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
181 lines
6.3 KiB
Python
181 lines
6.3 KiB
Python
import ctypes
|
|
|
|
import windows
|
|
import windows.generated_def as gdef
|
|
|
|
from windows import utils
|
|
|
|
|
|
def get_api_set_map_for_current_process(base):
|
|
base = windows.current_process.peb.ApiSetMap
|
|
version = windows.current_process.read_dword(base)
|
|
if version not in API_SET_MAP_BY_VERSION:
|
|
raise NotImplementedError("ApiSetMap version <{0}> not implemented, please contact me, I need a sample to implement it ;)")
|
|
return API_SET_MAP_BY_VERSION[version](base)
|
|
|
|
|
|
class ApiSetMap(object):
|
|
"""The base class for the ApiSeMap
|
|
(see `Runtime DLL name resolution: ApiSetSchema <https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-ii.html>`_)
|
|
"""
|
|
version = None #: The version of the ApiSetMap
|
|
|
|
def __init__(self, base):
|
|
self.base = base
|
|
self.target = windows.current_process
|
|
|
|
# helpers
|
|
def read_apiset_wstring(self, offset, length):
|
|
return self.target.read_memory(self.base + offset, length).decode("utf-16")
|
|
|
|
# Low-level version-dependent parsing function
|
|
def entries_array(self):
|
|
raise NotImplementedError("Should be implemented by subclasses")
|
|
|
|
def get_entry_name(self, entry):
|
|
raise NotImplementedError("Should be implemented by subclasses")
|
|
|
|
def get_entry_name_basicimpl(self, entry):
|
|
return self.read_apiset_wstring(entry.NameOffset, entry.NameLength)
|
|
|
|
def values_for_entry(self, entry):
|
|
raise NotImplementedError("Should be implemented by subclasses")
|
|
|
|
@utils.fixedpropety
|
|
def apisetmap_dict(self):
|
|
"""The apisetmap dll-mapping content extracted from memory as a :class:`dict`
|
|
|
|
``key -> value example``::
|
|
|
|
u'ext-ms-win-advapi32-encryptedfile-l1-1-1' -> u'advapi32.dll'
|
|
"""
|
|
res = {}
|
|
for entry in self.entries_array():
|
|
values = self.values_for_entry(entry)
|
|
if not values:
|
|
final_value = None
|
|
else:
|
|
final_value = values[-1]
|
|
res[self.get_entry_name(entry)] = final_value
|
|
return res
|
|
|
|
@utils.fixedpropety
|
|
def resolution_dict(self):
|
|
"""The :class:`dict` based on :obj:`apisetmap_dict` with only the part checked by ``Windows``.
|
|
|
|
``Windows`` does not care about what is after the last ``-``
|
|
|
|
``key -> value example``::
|
|
|
|
u'ext-ms-win-advapi32-encryptedfile-l1-1-' -> u'advapi32.dll'
|
|
|
|
"""
|
|
res = {}
|
|
for name, resolved_name in self.apisetmap_dict.items():
|
|
# ApiSetResolveToHost does not care about last version + extension
|
|
# It remove everything after the last '-'
|
|
|
|
# Possible to have no '-' ?
|
|
try:
|
|
cutname = name[:name.rindex("-") + 1]
|
|
except ValueError as e:
|
|
cutname = name
|
|
res[cutname] = resolved_name
|
|
return res
|
|
|
|
def resolve(self, dllname):
|
|
"""The method used to resolve a DLL name using the ApiSetMap.
|
|
The behavior should match the non-exported function ``ntdll!ApiSetResolveToHost``
|
|
"""
|
|
try:
|
|
cutname = dllname[:dllname.rindex("-") + 1]
|
|
except ValueError as e:
|
|
return None
|
|
return self.resolution_dict[cutname]
|
|
|
|
|
|
|
|
class ApiSetMapVersion2(ApiSetMap):
|
|
"""Represent an ApiSetMap version-2"""
|
|
version = 2 #: The version of the ApiSetMap
|
|
|
|
def namespace(self):
|
|
return gdef.API_SET_NAMESPACE_ARRAY_V2.from_address(self.base)
|
|
|
|
def entries_array(self):
|
|
namespace = self.namespace()
|
|
array_addr = ctypes.addressof(namespace.Array)
|
|
array_size = namespace.Count
|
|
return (gdef.API_SET_NAMESPACE_ENTRY_V2 * array_size).from_address(array_addr)
|
|
|
|
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
|
|
|
def values_for_entry(self, entry):
|
|
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V2).from_address(self.base + entry.DataOffset)
|
|
array_size = values_array_v2.Count
|
|
array_addr = ctypes.addressof(values_array_v2.Array)
|
|
values_array = (gdef.API_SET_VALUE_ENTRY_V2 * array_size).from_address(array_addr)
|
|
res = []
|
|
for value in values_array:
|
|
if value.ValueLength:
|
|
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
|
res.append(v)
|
|
return res
|
|
|
|
|
|
class ApiSetMapVersion4(ApiSetMap):
|
|
"""Represent an ApiSetMap version-4"""
|
|
version = 4 #: The version of the ApiSetMap
|
|
|
|
def namespace(self):
|
|
return gdef.API_SET_NAMESPACE_ARRAY_V4.from_address(self.base)
|
|
|
|
def entries_array(self):
|
|
namespace = self.namespace()
|
|
array_addr = ctypes.addressof(namespace.Array)
|
|
array_size = namespace.Count
|
|
return (gdef.API_SET_NAMESPACE_ENTRY_V4 * array_size).from_address(array_addr)
|
|
|
|
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
|
|
|
def values_for_entry(self, entry):
|
|
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V4).from_address(self.base + entry.DataOffset)
|
|
array_size = values_array_v2.Count
|
|
array_addr = ctypes.addressof(values_array_v2.Array)
|
|
values_array = (gdef.API_SET_VALUE_ENTRY * array_size).from_address(array_addr)
|
|
res = []
|
|
for value in values_array:
|
|
if value.ValueLength:
|
|
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
|
res.append(v)
|
|
return res
|
|
|
|
class ApiSetMapVersion6(ApiSetMap):
|
|
"""Represent an ApiSetMap version-6"""
|
|
version = 6 #: The version of the ApiSetMap
|
|
|
|
def namespace(self):
|
|
return gdef.API_SET_NAMESPACE_V6.from_address(self.base)
|
|
|
|
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
|
|
|
def entries_array(self):
|
|
namespace = self.namespace()
|
|
array_offset = namespace.EntryOffset
|
|
array_size = namespace.Count
|
|
return (gdef.API_SET_NAMESPACE_ENTRY_V6 * array_size).from_address(self.base + array_offset)
|
|
|
|
def values_for_entry(self, entry):
|
|
values_array = (gdef.API_SET_VALUE_ENTRY * entry.ValueCount).from_address(self.base + entry.ValueOffset)
|
|
res = []
|
|
for value in values_array:
|
|
if value.ValueLength:
|
|
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
|
res.append(v)
|
|
return res
|
|
|
|
API_SET_MAP_BY_VERSION = {
|
|
2: ApiSetMapVersion2,
|
|
4: ApiSetMapVersion4,
|
|
6: ApiSetMapVersion6,
|
|
} |