cosmetic and typing-related changes
This commit is contained in:
parent
9759645f92
commit
6913f73db4
@ -2,7 +2,7 @@
|
|||||||
Abstract class for cross-platform memory editing.
|
Abstract class for cross-platform memory editing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, Optional, Union, Generator
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import copy
|
import copy
|
||||||
@ -23,8 +23,8 @@ class Process(metaclass=ABCMeta):
|
|||||||
(i.e., by reading from or writing to the memory used by a given process).
|
(i.e., by reading from or writing to the memory used by a given process).
|
||||||
|
|
||||||
The static methods
|
The static methods
|
||||||
Process.list_available_pids()
|
`Process.list_available_pids()`
|
||||||
Process.get_pid_by_name(executable_filename)
|
`Process.get_pid_by_name(executable_filename)`
|
||||||
can be used to help find the process id (pid) of the target process. They are
|
can be used to help find the process id (pid) of the target process. They are
|
||||||
provided for convenience only; it is probably better to use the tools built
|
provided for convenience only; it is probably better to use the tools built
|
||||||
in to your operating system to discover the pid of the specific process you
|
in to your operating system to discover the pid of the specific process you
|
||||||
@ -32,18 +32,19 @@ class Process(metaclass=ABCMeta):
|
|||||||
|
|
||||||
Once you have found the pid, you are ready to construct an instance of Process
|
Once you have found the pid, you are ready to construct an instance of Process
|
||||||
and use it to read and write to memory. Once you are done with the process,
|
and use it to read and write to memory. Once you are done with the process,
|
||||||
use .close() to free up the process for access by other debuggers etc.
|
use `.close()` to free up the process for access by other debuggers etc.
|
||||||
|
```
|
||||||
p = Process(1239)
|
p = Process(1239)
|
||||||
p.close()
|
p.close()
|
||||||
|
```
|
||||||
|
|
||||||
To read/write to memory, first create a buffer using ctypes:
|
To read/write to memory, first create a buffer using ctypes:
|
||||||
|
```
|
||||||
buffer0 = (ctypes.c_byte * 5)(39, 50, 03, 40, 30)
|
buffer0 = (ctypes.c_byte * 5)(39, 50, 03, 40, 30)
|
||||||
buffer1 = ctypes.c_ulong()
|
buffer1 = ctypes.c_ulong()
|
||||||
|
```
|
||||||
and then use
|
and then use
|
||||||
|
```
|
||||||
p.write_memory(0x2fe, buffer0)
|
p.write_memory(0x2fe, buffer0)
|
||||||
|
|
||||||
val0 = p.read_memory(0x220, buffer0)[:]
|
val0 = p.read_memory(0x220, buffer0)[:]
|
||||||
@ -51,52 +52,52 @@ class Process(metaclass=ABCMeta):
|
|||||||
val1a = p.read_memory(0x149, buffer1).value
|
val1a = p.read_memory(0x149, buffer1).value
|
||||||
val2b = buffer1.value
|
val2b = buffer1.value
|
||||||
assert(val1a == val2b)
|
assert(val1a == val2b)
|
||||||
|
```
|
||||||
|
|
||||||
Searching for a value can be done in a number of ways:
|
Searching for a value can be done in a number of ways:
|
||||||
Search a list of addresses:
|
Search a list of addresses:
|
||||||
found_addresses = p.search_addresses([0x1020, 0x1030], buffer0)
|
`found_addresses = p.search_addresses([0x1020, 0x1030], buffer0)`
|
||||||
Search the entire memory space:
|
Search the entire memory space:
|
||||||
found_addresses = p.search_all_memory(buffer0, writeable_only=False)
|
`found_addresses = p.search_all_memory(buffer0, writeable_only=False)`
|
||||||
|
|
||||||
You can also get a list of which regions in memory are mapped (readable):
|
You can also get a list of which regions in memory are mapped (readable):
|
||||||
regions = p.list_mapped_regions(writeable_only=False)
|
`regions = p.list_mapped_regions(writeable_only=False)`
|
||||||
|
|
||||||
which can be used along with search_buffer(...) to re-create .search_all_memory(...):
|
which can be used along with search_buffer(...) to re-create .search_all_memory(...):
|
||||||
|
```
|
||||||
found = []
|
found = []
|
||||||
for region_start, region_stop in regions:
|
for region_start, region_stop in regions:
|
||||||
region_buffer = (ctypes.c_byte * (region_stop - region_start))()
|
region_buffer = (ctypes.c_byte * (region_stop - region_start))()
|
||||||
p.read_memory(region_start, region_buffer)
|
p.read_memory(region_start, region_buffer)
|
||||||
found += utils.search_buffer(ctypes.c_ulong(123456790), region_buffer)
|
found += utils.search_buffer(ctypes.c_ulong(123456790), region_buffer)
|
||||||
|
```
|
||||||
Other useful methods include the context manager, implemented as a static method:
|
Other useful methods include the context manager, implemented as a static method:
|
||||||
|
```
|
||||||
with Process.open_process(pid) as p:
|
with Process.open_process(pid) as p:
|
||||||
# use p here, no need to call p.close()
|
# use p here, no need to call p.close()
|
||||||
|
```
|
||||||
.get_path(), which reports the path of the executable file which was used
|
.get_path(), which reports the path of the executable file which was used
|
||||||
to start the process:
|
to start the process:
|
||||||
|
```
|
||||||
executable_path = p.get_path()
|
executable_path = p.get_path()
|
||||||
|
```
|
||||||
and deref_struct_pointer, which takes a pointer to a struct and reads out the struct members:
|
and deref_struct_pointer, which takes a pointer to a struct and reads out the struct members:
|
||||||
|
```
|
||||||
# struct is a list of (offset, buffer) pairs
|
# struct is a list of (offset, buffer) pairs
|
||||||
struct_defintion = [(0x0, ctypes.c_ulong()),
|
struct_defintion = [(0x0, ctypes.c_ulong()),
|
||||||
(0x20, ctypes.c_byte())]
|
(0x20, ctypes.c_byte())]
|
||||||
values = p.deref_struct_pointer(0x0feab4, struct_defintion)
|
values = p.deref_struct_pointer(0x0feab4, struct_defintion)
|
||||||
|
```
|
||||||
which is shorthand for
|
which is shorthand for
|
||||||
|
```
|
||||||
struct_addr = p.read_memory(0x0feab4, ctypes.c_void_p())
|
struct_addr = p.read_memory(0x0feab4, ctypes.c_void_p())
|
||||||
values = [p.read_memory(struct_addr + 0x0, ctypes.c_ulong()),
|
values = [p.read_memory(struct_addr + 0x0, ctypes.c_ulong()),
|
||||||
p.read_memory(struct_addr + 0x20, ctypes.c_byte())]
|
p.read_memory(struct_addr + 0x20, ctypes.c_byte())]
|
||||||
|
```
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Putting all this together, a simple program which alters a magic number in the only running
|
Putting all this together, a simple program which alters a magic number in the only running
|
||||||
instance of 'magic.exe' might look like this:
|
instance of 'magic.exe' might look like this:
|
||||||
|
```
|
||||||
import ctypes
|
import ctypes
|
||||||
from mem_edit import Process
|
from mem_edit import Process
|
||||||
|
|
||||||
@ -107,9 +108,9 @@ class Process(metaclass=ABCMeta):
|
|||||||
addrs = p.search_all_memory(magic_number)
|
addrs = p.search_all_memory(magic_number)
|
||||||
assert(len(addrs) == 1)
|
assert(len(addrs) == 1)
|
||||||
p.write_memory(addrs[0], ctypes.c_ulong(42))
|
p.write_memory(addrs[0], ctypes.c_ulong(42))
|
||||||
|
```
|
||||||
Searching for a value which changes:
|
Searching for a value which changes:
|
||||||
|
```
|
||||||
pid = Process.get_pid_by_name('monitor_me.exe')
|
pid = Process.get_pid_by_name('monitor_me.exe')
|
||||||
with Process.open_process(pid) as p:
|
with Process.open_process(pid) as p:
|
||||||
addrs = p.search_all_memory(ctypes.c_int(40))
|
addrs = p.search_all_memory(ctypes.c_int(40))
|
||||||
@ -118,18 +119,19 @@ class Process(metaclass=ABCMeta):
|
|||||||
print('Found addresses:')
|
print('Found addresses:')
|
||||||
for addr in filtered_addrs:
|
for addr in filtered_addrs:
|
||||||
print(hex(addr))
|
print(hex(addr))
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __init__(self, process_id: int):
|
def __init__(self, process_id: int):
|
||||||
"""
|
"""
|
||||||
Constructing a Process object prepares the process with specified process_id for
|
Constructing a Process object prepares the process with specified process_id for
|
||||||
memory editing. Finding the process_id for the process you want to edit is often
|
memory editing. Finding the `process_id` for the process you want to edit is often
|
||||||
easiest using os-specific tools (or by launching the process yourself, e.g. with
|
easiest using os-specific tools (or by launching the process yourself, e.g. with
|
||||||
subprocess.Popen(...)).
|
`subprocess.Popen(...)`).
|
||||||
|
|
||||||
:param process_id: Process id (pid) of the target process
|
Args:
|
||||||
|
process_id: Process id (pid) of the target process
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -140,7 +142,7 @@ class Process(metaclass=ABCMeta):
|
|||||||
letting other debuggers attach to it instead.
|
letting other debuggers attach to it instead.
|
||||||
|
|
||||||
This function should be called after you are done working with the process
|
This function should be called after you are done working with the process
|
||||||
and will no longer need it. See the Process.open_process(...) context
|
and will no longer need it. See the `Process.open_process(...)` context
|
||||||
manager to avoid having to call this function yourself.
|
manager to avoid having to call this function yourself.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
@ -148,38 +150,45 @@ class Process(metaclass=ABCMeta):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t):
|
def write_memory(self, base_address: int, write_buffer: ctypes_buffer_t):
|
||||||
"""
|
"""
|
||||||
Write the given buffer to the process's address space, starting at base_address.
|
Write the given buffer to the process's address space, starting at `base_address`.
|
||||||
|
|
||||||
:param base_address: The address to write at, in the process's address space.
|
Args:
|
||||||
:param write_buffer: A ctypes object, for example, ctypes.c_ulong(48),
|
base_address: The address to write at, in the process's address space.
|
||||||
(ctypes.c_byte * 3)(43, 21, 0xff), or a subclass of ctypes.Structure,
|
write_buffer: A ctypes object, for example, `ctypes.c_ulong(48)`,
|
||||||
which will be written into memory starting at base_address.
|
`(ctypes.c_byte * 3)(43, 21, 0xff)`, or a subclass of `ctypes.Structure`,
|
||||||
|
which will be written into memory starting at `base_address`.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t:
|
def read_memory(self, base_address: int, read_buffer: ctypes_buffer_t) -> ctypes_buffer_t:
|
||||||
"""
|
"""
|
||||||
Read into the given buffer from the process's address space, starting at base_address.
|
Read into the given buffer from the process's address space, starting at `base_address`.
|
||||||
|
|
||||||
:param base_address: The address to read from, in the process's address space.
|
Args:
|
||||||
:param read_buffer: A ctypes object, for example. ctypes.c_ulong(),
|
base_address: The address to read from, in the process's address space.
|
||||||
(ctypes.c_byte * 3)(), or a subclass of ctypes.Structure, which will be
|
read_buffer: A `ctypes` object, for example. `ctypes.c_ulong()`,
|
||||||
overwritten with the contents of the process's memory starting at base_address.
|
`(ctypes.c_byte * 3)()`, or a subclass of `ctypes.Structure`, which will be
|
||||||
:returns: read_buffer is returned as well as being overwritten.
|
overwritten with the contents of the process's memory starting at `base_address`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`read_buffer` is returned as well as being overwritten.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def list_mapped_regions(self, writeable_only=True) -> List[Tuple[int, int]]:
|
def list_mapped_regions(self, writeable_only=True) -> List[Tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Return a list of (start_address, stop_address) for the regions of the address space
|
Return a list of `(start_address, stop_address)` for the regions of the address space
|
||||||
accessible to (readable and possibly writable by) the process.
|
accessible to (readable and possibly writable by) the process.
|
||||||
By default, this function does not return non-writeable regions.
|
By default, this function does not return non-writeable regions.
|
||||||
|
|
||||||
:param writeable_only: If True, only return regions which are also writeable.
|
Args:
|
||||||
Default true.
|
writeable_only: If `True`, only return regions which are also writeable.
|
||||||
:return: List of (start_address, stop_address) for each accessible memory region.
|
Default `True`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of `(start_address, stop_address)` for each accessible memory region.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -188,7 +197,8 @@ class Process(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Return the path to the executable file which was run to start this process.
|
Return the path to the executable file which was run to start this process.
|
||||||
|
|
||||||
:return: A string containing the path.
|
Returns:
|
||||||
|
A string containing the path.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -198,13 +208,14 @@ class Process(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Return a list of all process ids (pids) accessible on this system.
|
Return a list of all process ids (pids) accessible on this system.
|
||||||
|
|
||||||
:return: List of running process ids.
|
Returns:
|
||||||
|
List of running process ids.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_pid_by_name(target_name: str) -> int or None:
|
def get_pid_by_name(target_name: str) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Attempt to return the process id (pid) of a process which was run with an executable
|
Attempt to return the process id (pid) of a process which was run with an executable
|
||||||
file with the provided name. If no process is found, return None.
|
file with the provided name. If no process is found, return None.
|
||||||
@ -215,7 +226,12 @@ class Process(metaclass=ABCMeta):
|
|||||||
Don't rely on this method if you can possibly avoid it, since it makes no
|
Don't rely on this method if you can possibly avoid it, since it makes no
|
||||||
attempt to confirm that it found a unique process and breaks trivially (e.g. if the
|
attempt to confirm that it found a unique process and breaks trivially (e.g. if the
|
||||||
executable file is renamed).
|
executable file is renamed).
|
||||||
:return: Process id (pid) of a process with the provided name, or None.
|
|
||||||
|
Args:
|
||||||
|
target_name: Name of the process to find the PID for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Process id (pid) of a process with the provided name, or `None`.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -225,34 +241,48 @@ class Process(metaclass=ABCMeta):
|
|||||||
) -> List[ctypes_buffer_t]:
|
) -> List[ctypes_buffer_t]:
|
||||||
"""
|
"""
|
||||||
Take a pointer to a struct and read out the struct members:
|
Take a pointer to a struct and read out the struct members:
|
||||||
|
```
|
||||||
struct_defintion = [(0x0, ctypes.c_ulong()),
|
struct_defintion = [(0x0, ctypes.c_ulong()),
|
||||||
(0x20, ctypes.c_byte())]
|
(0x20, ctypes.c_byte())]
|
||||||
values = p.deref_struct_pointer(0x0feab4, struct_defintion)
|
values = p.deref_struct_pointer(0x0feab4, struct_defintion)
|
||||||
|
```
|
||||||
which is shorthand for
|
which is shorthand for
|
||||||
|
```
|
||||||
struct_addr = p.read_memory(0x0feab4, ctypes.c_void_p())
|
struct_addr = p.read_memory(0x0feab4, ctypes.c_void_p())
|
||||||
values = [p.read_memory(struct_addr + 0x0, ctypes.c_ulong()),
|
values = [p.read_memory(struct_addr + 0x0, ctypes.c_ulong()),
|
||||||
p.read_memory(struct_addr + 0x20, ctypes.c_byte())]
|
p.read_memory(struct_addr + 0x20, ctypes.c_byte())]
|
||||||
|
```
|
||||||
|
|
||||||
:param base_address: Address at which the struct pointer is located.
|
Args:
|
||||||
:param targets: List of (offset, read_buffer) pairs which will be read from the struct.
|
base_address: Address at which the struct pointer is located.
|
||||||
:return: List of read values corresponding to the provided targets.
|
targets: List of `(offset, read_buffer)` pairs which will be read from the struct.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
List of read values corresponding to the provided targets.
|
||||||
"""
|
"""
|
||||||
base = self.read_memory(base_address, ctypes.c_void_p()).value
|
base = self.read_memory(base_address, ctypes.c_void_p()).value
|
||||||
values = [self.read_memory(base + offset, buffer) for offset, buffer in targets]
|
values = [self.read_memory(base + offset, buffer) for offset, buffer in targets]
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def search_addresses(self, addresses: List[int], needle_buffer: ctypes_buffer_t, verbatim: bool=True) -> List[int]:
|
def search_addresses(self,
|
||||||
|
addresses: List[int],
|
||||||
|
needle_buffer: ctypes_buffer_t,
|
||||||
|
verbatim: bool = True,
|
||||||
|
) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search for the provided value at each of the provided addresses, and return the addresses
|
Search for the provided value at each of the provided addresses, and return the addresses
|
||||||
where it is found.
|
where it is found.
|
||||||
|
|
||||||
:param addresses: List of addresses which should be probed.
|
Args:
|
||||||
:param needle_buffer: The value to search for. This should be a ctypes object of the same
|
addresses: List of addresses which should be probed.
|
||||||
sorts as used by .read_memory(...), which will be compared to the contents of
|
needle_buffer: The value to search for. This should be a `ctypes` object of the same
|
||||||
|
sorts as used by `.read_memory(...)`, which will be compared to the contents of
|
||||||
memory at each of the given addresses.
|
memory at each of the given addresses.
|
||||||
:param verbatim: If True, perform bitwise comparison when searching for needle_buffer.
|
verbatim: If `True`, perform bitwise comparison when searching for `needle_buffer`.
|
||||||
If False, perform utils.ctypes_equal-based comparison. Default True.
|
If `False`, perform `utils.ctypes_equal`-based comparison. Default `True`.
|
||||||
:return: List of addresses where the needle_buffer was found.
|
|
||||||
|
Returns:
|
||||||
|
List of addresses where the `needle_buffer` was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
read_buffer = copy.copy(needle_buffer)
|
read_buffer = copy.copy(needle_buffer)
|
||||||
@ -269,18 +299,25 @@ class Process(metaclass=ABCMeta):
|
|||||||
found.append(address)
|
found.append(address)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
def search_all_memory(self, needle_buffer: ctypes_buffer_t, writeable_only: bool=True, verbatim: bool=True) -> List[int]:
|
def search_all_memory(self,
|
||||||
|
needle_buffer: ctypes_buffer_t,
|
||||||
|
writeable_only: bool = True,
|
||||||
|
verbatim: bool = True,
|
||||||
|
) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search the entire memory space accessible to the process for the provided value.
|
Search the entire memory space accessible to the process for the provided value.
|
||||||
|
|
||||||
:param needle_buffer: The value to search for. This should be a ctypes object of the same
|
Args:
|
||||||
sorts as used by .read_memory(...), which will be compared to the contents of
|
needle_buffer: The value to search for. This should be a ctypes object of the same
|
||||||
|
sorts as used by `.read_memory(...)`, which will be compared to the contents of
|
||||||
memory at each accessible address.
|
memory at each accessible address.
|
||||||
:param writeable_only: If True, only search regions where the process has write access.
|
writeable_only: If `True`, only search regions where the process has write access.
|
||||||
Default True.
|
Default `True`.
|
||||||
:param verbatim: If True, perform bitwise comparison when searching for needle_buffer.
|
verbatim: If `True`, perform bitwise comparison when searching for `needle_buffer`.
|
||||||
If False, perform utils.ctypes_equal-based comparison. Default True.
|
If `False`, perform `utils.ctypes_equal-based` comparison. Default `True`.
|
||||||
:return: List of addresses where the needle_buffer was found.
|
|
||||||
|
Returns:
|
||||||
|
List of addresses where the `needle_buffer` was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
if verbatim:
|
if verbatim:
|
||||||
@ -299,15 +336,20 @@ class Process(metaclass=ABCMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def open_process(cls, process_id: int) -> 'Process':
|
def open_process(cls, process_id: int) -> Generator['Process', None, None]:
|
||||||
"""
|
"""
|
||||||
Context manager which automatically closes the constructed Process:
|
Context manager which automatically closes the constructed Process:
|
||||||
|
```
|
||||||
with Process.open_process(2394) as p:
|
with Process.open_process(2394) as p:
|
||||||
# use p here
|
# use p here
|
||||||
# no need to run p.close()
|
# no need to run p.close()
|
||||||
|
```
|
||||||
|
|
||||||
:param process_id: Process id (pid), passed to the Process constructor.
|
Args:
|
||||||
:return: Constructed Process object.
|
process_id: Process id (pid), passed to the Process constructor.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Constructed Process object.
|
||||||
"""
|
"""
|
||||||
process = cls(process_id)
|
process = cls(process_id)
|
||||||
yield process
|
yield process
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Implementation of Process class for Linux
|
Implementation of Process class for Linux
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, Optional
|
||||||
from os import strerror
|
from os import strerror
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
@ -91,7 +91,7 @@ class Process(AbstractProcess):
|
|||||||
return pids
|
return pids
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pid_by_name(target_name: str) -> int or None:
|
def get_pid_by_name(target_name: str) -> Optional[int]:
|
||||||
for pid in Process.list_available_pids():
|
for pid in Process.list_available_pids():
|
||||||
try:
|
try:
|
||||||
logger.info('Checking name for pid {}'.format(pid))
|
logger.info('Checking name for pid {}'.format(pid))
|
||||||
|
@ -12,24 +12,29 @@ Utility functions and types:
|
|||||||
ctypes_equal(a, b)
|
ctypes_equal(a, b)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Union
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
ctypes_buffer_t = ctypes._SimpleCData or ctypes.Array or ctypes.Structure or ctypes.Union
|
ctypes_buffer_t = Union[ctypes._SimpleCData, ctypes.Array, ctypes.Structure, ctypes.Union]
|
||||||
|
|
||||||
|
|
||||||
class MemEditError(Exception):
|
class MemEditError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def search_buffer_verbatim(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer_t) -> List[int]:
|
def search_buffer_verbatim(needle_buffer: ctypes_buffer_t,
|
||||||
|
haystack_buffer: ctypes_buffer_t,
|
||||||
|
) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search for a buffer inside another buffer, using a direct (bitwise) comparison
|
Search for a buffer inside another buffer, using a direct (bitwise) comparison
|
||||||
|
|
||||||
:param needle_buffer: Buffer to search for.
|
Args:
|
||||||
:param haystack_buffer: Buffer to search in.
|
needle_buffer: Buffer to search for.
|
||||||
:return: List of offsets where the needle_buffer was found.
|
haystack_buffer: Buffer to search in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of offsets where the `needle_buffer` was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
@ -45,14 +50,19 @@ def search_buffer_verbatim(needle_buffer: ctypes_buffer_t, haystack_buffer: ctyp
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
def search_buffer(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer_t) -> List[int]:
|
def search_buffer(needle_buffer: ctypes_buffer_t,
|
||||||
|
haystack_buffer: ctypes_buffer_t,
|
||||||
|
) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Search for a buffer inside another buffer, using ctypes_equal for comparison.
|
Search for a buffer inside another buffer, using `ctypes_equal` for comparison.
|
||||||
Much slower than search_buffer_verbatim.
|
Much slower than `search_buffer_verbatim`.
|
||||||
|
|
||||||
:param needle_buffer: Buffer to search for.
|
Args:
|
||||||
:param haystack_buffer: Buffer to search in.
|
needle_buffer: Buffer to search for.
|
||||||
:return: List of offsets where the needle_buffer was found.
|
haystack_buffer: Buffer to search in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of offsets where the needle_buffer was found.
|
||||||
"""
|
"""
|
||||||
found = []
|
found = []
|
||||||
read_type = type(needle_buffer)
|
read_type = type(needle_buffer)
|
||||||
@ -63,7 +73,9 @@ def search_buffer(needle_buffer: ctypes_buffer_t, haystack_buffer: ctypes_buffer
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
def ctypes_equal(a: ctypes_buffer_t, b: ctypes_buffer_t) -> bool:
|
def ctypes_equal(a: ctypes_buffer_t,
|
||||||
|
b: ctypes_buffer_t,
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the values stored inside two ctypes buffers are equal.
|
Check if the values stored inside two ctypes buffers are equal.
|
||||||
"""
|
"""
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Implementation of Process class for Windows
|
Implementation of Process class for Windows
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, Optional
|
||||||
from math import floor
|
from math import floor
|
||||||
from os import strerror
|
from os import strerror
|
||||||
import os.path
|
import os.path
|
||||||
@ -98,16 +98,19 @@ if PTR_SIZE == 8: # 64-bit python
|
|||||||
elif PTR_SIZE == 4: # 32-bit python
|
elif PTR_SIZE == 4: # 32-bit python
|
||||||
MEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION32
|
MEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION32
|
||||||
|
|
||||||
ctypes.windll.kernel32.VirtualQueryEx.argtypes = [ctypes.wintypes.HANDLE,
|
ctypes.windll.kernel32.VirtualQueryEx.argtypes = [
|
||||||
|
ctypes.wintypes.HANDLE,
|
||||||
ctypes.wintypes.LPCVOID,
|
ctypes.wintypes.LPCVOID,
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_size_t]
|
ctypes.c_size_t]
|
||||||
ctypes.windll.kernel32.ReadProcessMemory.argtypes = [ctypes.wintypes.HANDLE,
|
ctypes.windll.kernel32.ReadProcessMemory.argtypes = [
|
||||||
|
ctypes.wintypes.HANDLE,
|
||||||
ctypes.wintypes.LPCVOID,
|
ctypes.wintypes.LPCVOID,
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_size_t,
|
ctypes.c_size_t,
|
||||||
ctypes.c_void_p]
|
ctypes.c_void_p]
|
||||||
ctypes.windll.kernel32.WriteProcessMemory.argtypes = [ctypes.wintypes.HANDLE,
|
ctypes.windll.kernel32.WriteProcessMemory.argtypes = [
|
||||||
|
ctypes.wintypes.HANDLE,
|
||||||
ctypes.wintypes.LPCVOID,
|
ctypes.wintypes.LPCVOID,
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_size_t,
|
ctypes.c_size_t,
|
||||||
@ -186,8 +189,7 @@ class Process(AbstractProcess):
|
|||||||
rval = ctypes.windll.psapi.GetProcessImageFileNameA(
|
rval = ctypes.windll.psapi.GetProcessImageFileNameA(
|
||||||
self.process_handle,
|
self.process_handle,
|
||||||
name_buffer,
|
name_buffer,
|
||||||
max_path_len
|
max_path_len)
|
||||||
)
|
|
||||||
|
|
||||||
if rval > 0:
|
if rval > 0:
|
||||||
return name_buffer.value.decode()
|
return name_buffer.value.decode()
|
||||||
@ -224,7 +226,7 @@ class Process(AbstractProcess):
|
|||||||
return pids[:num_returned]
|
return pids[:num_returned]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pid_by_name(target_name: str) -> int or None:
|
def get_pid_by_name(target_name: str) -> Optional[int]:
|
||||||
for pid in Process.list_available_pids():
|
for pid in Process.list_available_pids():
|
||||||
try:
|
try:
|
||||||
logger.info('Checking name for pid {}'.format(pid))
|
logger.info('Checking name for pid {}'.format(pid))
|
||||||
@ -263,8 +265,7 @@ class Process(AbstractProcess):
|
|||||||
self.process_handle,
|
self.process_handle,
|
||||||
address,
|
address,
|
||||||
mbi_ptr,
|
mbi_ptr,
|
||||||
mbi_size,
|
mbi_size)
|
||||||
)
|
|
||||||
|
|
||||||
if success != mbi_size:
|
if success != mbi_size:
|
||||||
if success == 0:
|
if success == 0:
|
||||||
@ -279,10 +280,11 @@ class Process(AbstractProcess):
|
|||||||
page_ptr = start
|
page_ptr = start
|
||||||
while page_ptr < stop:
|
while page_ptr < stop:
|
||||||
page_info = get_mem_info(page_ptr)
|
page_info = get_mem_info(page_ptr)
|
||||||
if page_info.Type == mem_types['MEM_PRIVATE'] and \
|
if (page_info.Type == mem_types['MEM_PRIVATE']
|
||||||
page_info.State == mem_states['MEM_COMMIT'] and \
|
and page_info.State == mem_states['MEM_COMMIT']
|
||||||
page_info.Protect & page_protections['PAGE_READABLE'] != 0 and \
|
and page_info.Protect & page_protections['PAGE_READABLE'] != 0
|
||||||
(page_info.Protect & page_protections['PAGE_READWRITEABLE'] != 0 or not writeable_only):
|
and (page_info.Protect & page_protections['PAGE_READWRITEABLE'] != 0
|
||||||
|
or not writeable_only)):
|
||||||
regions.append((page_ptr, page_ptr + page_info.RegionSize))
|
regions.append((page_ptr, page_ptr + page_info.RegionSize))
|
||||||
page_ptr += page_info.RegionSize
|
page_ptr += page_info.RegionSize
|
||||||
|
|
||||||
|
3
setup.py
3
setup.py
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
with open('README.md', 'r') as f:
|
|
||||||
|
with open('README.md', 'rt') as f:
|
||||||
long_description = f.read()
|
long_description = f.read()
|
||||||
|
|
||||||
with open('mem_edit/VERSION.py', 'rt') as f:
|
with open('mem_edit/VERSION.py', 'rt') as f:
|
||||||
|
Loading…
Reference in New Issue
Block a user