This commit is contained in:
Ben Lincoln committed 2022-10-03 13:11:46 -07:00
1 parent f32e99c316
commit e759efaeb2
5 files changed
+178 -26

No files matched your search

+53 -4
View File
@@ -12,8 +12,8 @@ BANNER = r"""
\/ \/\______| \/ \/ \/|__| \/
asminject.py
v0.40
Ben Lincoln, Bishop Fox, 2022-08-05
v0.41
Ben Lincoln, Bishop Fox, 2022-10-03
https://github.com/BishopFox/asminject
based on dlinject, which is Copyright (c) 2019 David Buchanan
dlinject source: https://github.com/DavidBuchanan314/dlinject
@@ -151,6 +151,24 @@ class asminject_state_codes:
self.message_type_map["pointer_to_string_data"] = self.state_map["switch_to_new_communication_address"] * 104
class asminject_parameters:
@staticmethod
def get_file_as_cstring(file_path):
result = ""
try:
with open(file_path, "rb") as input_file:
try:
current_byte = input_file.read(1)
while current_byte != b"":
result = result + "\\x" + '{0:0{1}x}'.format(int.from_bytes(current_byte, "little"), 2)
current_byte = input_file.read(1)
except Exception as e:
log_error(f"Couldn't read binary data from file '{file_path}': {e}", ansi=injection_params.ansi)
sys.exit(1)
except FileNotFoundError as e:
log_error(f"Couldn't open file '{file_path}': {e}", ansi=injection_params.ansi)
sys.exit(1)
return result
# because random.shuffle with a random function is deprecrated in Python 3.9
@staticmethod
def get_securely_shuffled_array(arr):
@@ -378,6 +396,11 @@ class asminject_parameters:
self.state_backup_restore_instruction_list = []
self.randomized_backup_restore_instruction_list = []
# ARM-specific options that are (more or less) independent of the overall architecture
self.arm_backup_s0_to_s31 = False
self.arm_backup_d16_to_d31 = False
# 512 is the minimum for the x86-64 fxsave instruction
#self.cpu_state_size = 1024
# Might need to make this dynamic based on the stack backup size
@@ -612,6 +635,10 @@ class asminject_parameters:
self.stack_pointer_register_name = "sp"
self.general_purpose_register_list = ["r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"]
self.state_backup_restore_instruction_list = [ ("stmdb sp!,{r0-r11}", "ldmia sp!, {r0-r11}") ]
if self.arm_backup_s0_to_s31:
self.state_backup_restore_instruction_list.append(("stmdb sp!,{s0-s31}", "ldmia sp!, {s0-s31}"))
if self.arm_backup_d16_to_d31:
self.state_backup_restore_instruction_list.append(("stmdb sp!,{d16-d31}", "ldmia sp!, {d16-d31}"))
self.flag_setting_instructions = [ "cmp", "cmn", "tst", "teq" ]
self.no_obfuscation_after_instructions = [ "ldr", "b", "bx", "bl", "blx"]
self.populate_randomized_state_backup_restore_list()
@@ -2027,7 +2054,7 @@ def asminject(injection_params):
time.sleep(0.1)
log("Process is frozen", ansi=injection_params.ansi)
elif injection_params.stop_method == "slow":
log("Switching to super slow motion, like every late 1990s/early 2000s action film director did after seeing _The Matrix_...", ansi=injection_params.ansi)
log("Dilating time relative to the target process...", ansi=injection_params.ansi)
try:
injection_params.set_dynamic_process_info_vars()
@@ -2172,7 +2199,7 @@ def asminject(injection_params):
# cleanup
os.rmdir(injection_params.freeze_dir)
elif injection_params.stop_method == "slow":
log("Returning to normal time", ansi=injection_params.ansi)
log("Un-dilating time relative to the target process", ansi=injection_params.ansi)
try:
injection_params.set_dynamic_process_info_vars()
output_process_priority_and_affinity(injection_params, "Post-injection")
@@ -2462,6 +2489,9 @@ if __name__ == "__main__":
parser.add_argument("--var",action='append',nargs=2, type=str,
help="Specify a custom variable for use by the stage 2 code, e.g. --var pythoncode \"print('OK')\". May be specified multiple times for different variables.")
parser.add_argument("--var-from-file",action='append',nargs=2, type=str,
help="Read the content of a file into a C-escaped string for use as a variable in the stage 2 code, e.g. --var-from-file pythoncode '/home/user/exploit_script.py'. May be specified multiple times for different variables.")
parser.add_argument("--plaintext", type=str2bool, nargs='?',
const=True, default=False,
@@ -2543,6 +2573,13 @@ if __name__ == "__main__":
parser.add_argument("--use-stage-2-source", type=str,
help="Debugging option for issue reproduction. Path to a pre-existing stage 2 assembly source code file, e.g. the result of adding --write-assembly-source-to-disk and --preserve-temp-files to a previous run of asminject.py. Note that you *must* use the '-post-obfuscation-pre-replacement.s' version of the file or injection will fail.")
parser.add_argument("--arm-backup-fpr-1", type=str2bool, nargs='?',
const=True, default=False,
help="For ARM processors specifically, back up the s0-s31 floating-point registers before executing the payload, and restore them afterward. Will cause an assembly failure if the registers don't exist on the target hardware.")
parser.add_argument("--arm-backup-fpr-2", type=str2bool, nargs='?',
const=True, default=False,
help="For ARM processors specifically, back up the d16-d31 floating-point registers before executing the payload, and restore them afterward. Will cause an assembly failure if the registers don't exist on the target hardware.")
args = parser.parse_args()
@@ -2552,6 +2589,12 @@ if __name__ == "__main__":
for var_set in args.var:
injection_params.custom_replacements[f"[VARIABLE:{var_set[0]}:VARIABLE]"] = var_set[1]
injection_params.custom_replacements[f"[VARIABLE:{var_set[0]}.length:VARIABLE]"] = str(len(var_set[1]))
if args.var_from_file:
for var_set in args.var_from_file:
file_content_escaped = asminject_parameters.get_file_as_cstring(var_set[1])
injection_params.custom_replacements[f"[VARIABLE:{var_set[0]}:VARIABLE]"] = file_content_escaped
injection_params.custom_replacements[f"[VARIABLE:{var_set[0]}.length:VARIABLE]"] = str(len(file_content_escaped))
# attempt to autodetermine architecture unless explicitly specified
architecture_string = ""
@@ -2570,6 +2613,12 @@ if __name__ == "__main__":
log(f"Using {autodetection_string}processor architecture '{architecture_string}'")
if args.arm_backup_fpr_1:
injection_params.arm_backup_s0_to_s31 = True
if args.arm_backup_fpr_2:
injection_params.arm_backup_d16_to_d31 = True
injection_params.base_script_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
injection_params.pid = args.pid
injection_params.set_architecture(architecture_string)
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+11 -17
View File
@@ -14,12 +14,10 @@ Often, using a standard Python decompilation tool such as [Decompyle++](https://
The `tools/python` directory of the `asminject.py` repository contains a Python script named `recursive_marshal.py` that will attempt to dump just about everything it can about a Python (including PyInstaller-generated binary) process. It is hardcoded to write output to a directory tree beginning with `/tmp/marshalled`, so edit the `recursive_marshal_base_dir` variable if you'd like it to go somewhere else. It should work with both Python 2 and 3, although it may require modifications for older minor releases.
Transform the script into a giant one-liner and then pass it to `asminject.py`, e.g.:
Pass the entire script to `asminject.py` using the `--var-from-file` option, e.g.:
```
# export PYTHONSCRIPT="`cat tools/python/recursive_marshal.py | sed -z 's/\n/\\\\n/g' | sed 's.".\\\\".g'`"
# python3 ./asminject.py 237465 execute_python_code.s --relative-offsets-from-binaries --var pythoncode "${PYTHONSCRIPT}"
# python3 ./asminject.py 237465 execute_python_code.s --relative-offsets-from-binaries --var-from-file pythoncode tools/python/recursive_marshal.py
```
If the target process is a regular Python script running in a Python interpreter, the output should include the original source code for any loaded modules. For example, consider the following output when the script was injected into used against the `practice/python_loop-with_library.py` script:
@@ -78,12 +76,10 @@ Launch the resulting `dist/python_loop-with_library/python_loop-with_library` bi
user 237465 [...] practice/dist/python_loop-with_library/python_loop-with_library
```
The process is virtually identical to extracting content from a regular Python process. Transform the `recursive_marshal.py` script (or `recursive_marshal.py`, for Python into a giant one-liner and then pass it to `asminject.py`, e.g.:
The process is virtually identical to extracting content from a regular Python process, e.g.:
```
# export PYTHONSCRIPT="`cat tools/python/recursive_marshal.py | sed -z 's/\n/\\\\n/g' | sed 's.".\\\\".g'`"
# python3 ./asminject.py 237465 execute_python_code.s --relative-offsets-from-binaries --var pythoncode "${PYTHONSCRIPT}"
# python3 ./asminject.py 237465 execute_python_code.s --relative-offsets-from-binaries --var-from-file pythoncode tools/python/recursive_marshal.py
```
For PyInstaller binaries, the source code will most likely not be present, and so the output will look like this:
@@ -329,7 +325,11 @@ maximum recursion depth exceeded while getting the repr of an object
maximum recursion depth exceeded while getting the str of an object
```
...when trying to generate the string/numeric representation of the object that the marshalling script uses to build its JSON output.
...when tryinvour()
```
The scripts will reconstruct these two statements as:
g to generate the string/numeric representation of the object that the marshalling script uses to build its JSON output.
This will cause function/method signatures to appear as `(<unknown>)`, and other values to appear as `"Unable to represent this value as a string"`.
@@ -341,11 +341,7 @@ If an object is instantiated directly in a module or class (as opposed to in a f
```
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
```
The scripts will reconstruct these two statements as:
_posix_flavour = _PosixFla
```
_windows_flavour = "<pathlib._WindowsFlavour object at 0x7f4fff561ba0>"
_posix_flavour = "<pathlib._PosixFlavour object at 0x7f4fff3ce800>"
@@ -386,14 +382,12 @@ Looks like the symbols `asminject.py` needs are still there, but does injecting
```
```
# export PYTHONSCRIPT="`cat tools/python/recursive_marshal.py| sed -z 's/\n/\\\\n/g' | sed 's.".\\\\".g'`"
# ps auxww | grep python_loop
user 945132 [...] practice/dist-linux/python_loop-with_library/python_loop-with_library
# python3 ./asminject.py 945132 execute_python_code.s \
--relative-offsets-from-binaries --var pythoncode "${PYTHONSCRIPT}"
--relative-offsets-from-binaries --var-from-file pythoncode tools/python/recursive_marshal.py
...omitted for brevity...
```
+108 -5
View File
@@ -3,12 +3,16 @@
<a href="../README.md">[ Back to the main README.md ]</a>
* [An introduction to code injection](#an-introduction-to-code-injection)
* [Determining which code to overwrite](#determining-which-code-to-overwrite)
* [Overwriting code without crashing the target process](#overwriting-code-without-crashing-the-target-process)
* [Reading and writing memory in another process](#reading-and-writing-memory-in-another-process)
* [Payload staging](#payload-staging)
* [Writeable versus executable memory](#writeable-versus-executable-memory)
* [Determining which code to overwrite](#determining-which-code-to-overwrite)
* [Building the code to inject](#building-the-code-to-inject)
* [Injecting code without destabilizing the target process](#injecting-code-without-destabilizing-the-target-process)
* [Communicating with the target process](#communicating-with-the-target-process)
* [Performing complex tasks inside the target process](#performing-complex-tasks-inside-the-target-process)
* [Cleaning up](#cleaning-up)
* [Obfuscation and anti-detection](#obfuscation-and-anti-detection)
## An introduction to code injection
@@ -40,15 +44,112 @@ In other words, by the time the last new instruction is executed, the target pro
This view still glosses over numerous important details, but those will be covered in the sections that follow.
## Reading and writing memory in another process
On Linux, there are two main options for accessing the memory space of another process:
* The `ptrace` debugging/tracing interface
* The `/proc/<pid>/mem` pseudofile
`ptrace` is the interface that debuggers like `gdb` use to interact with a process (more specifically, one or more threads of a process, but the distinction isn't important here). It has all kinds of neat features, like the ability to pause and resume execution, query the state of the CPU registers, and of course read from and write to the memory space of the target process. Unfortunately, only one "tracer" may be attached to a given "tracee". This means that software developers can make it more difficult to attach via `ptrace` by having their software attach *to itself* as a faux debugger, and verify that the attachment remains in place while the software runs. One could modify the software to bypass that protection, but that sounds like a continuous cat-and-mouse game that wouldn't be very fun. If it's the easiest way for a determined attacker to achieve their goal, the attacker will still *do* it, but in this case there's an easier approach.
`/proc` is the `procfs` interface in Linux - a virtual mapping of information about the system into the filesystem. For each process currently running on the system, `/proc` contains a directory named after the process' ID. Each of those directories contains a file named `mem` that is actually a mapping to the memory space for that process. If a process is running in a context that has permission to access the `mem` file for another process, it can open it as if it were a real file, then read and write data - as long as it only accesses offsets that are within memory ranges that the target process has actually mapped. The `/proc/<pid>/maps` pseudofile contains a list of those memory ranges for the corresponding process.
Interacting with the `mem` file for another process requires the same permission as using the `ptrace` interface (`CAP_SYS_PTRACE`), but multiple processes can access the `mem` file at the same time. Therefore, `asminject.py` can tamper with a process even if someone is debugging it using `gdb` at the same time.
## Payload staging
Injecting a small initial payload that leverages its foothold to inject a larger payload (as described in the previous section) is typically referred to as a "staged" approach, with the initial payload being referred to as a "stager".
`asminject.py` uses a two-stage model that was originally based on the code included with [dlinject](https://github.com/DavidBuchanan314/dlinject/). At a high level, it works like the hypothetical instructions in the [introduction to code injection](#an-introduction-to-code-injection) section, above. `asminject.py` overwrites some of the existing instructions in the target process with the first stage code. When the target process does something that would normally execute the original instructions, it instead triggers the first stage code.
`asminject.py` and the first stage work together to preserve the state of the target process as closely as possible (discussed in more detail in the [preserving program state](#preserving-program-state) and [preserving CPU state](#preserving-cpu-state) subsections, below) before taking any other action. This allows them to restore that state after the payload has finished executing, so that the original functionality of the target process typically remains unaffected. The first stage then allocates two new blocks of memory: one with read/execute permissions, and one with read/write permissions, as described further in the [writeable versus executable memory](#writeable-versus-executable-memory) section, below. The virtual addresses of the new blocks are unpredictable and different each time, so the first stage uses the mechanism discussed in the [communicating with the target process](#communicating-with-the-target-process) section to notify `asminject.py` where the new blocks are located, and that it's ready for `asminject.py` to write the second stage to the new read/execute block of memory.
`asminject.py` receives the notification, writes the second stage to the specified location, and then notifies the first stage that it can proceed. The first stage then executes the second stage code.
The second stage consists of a reusable template that performs actions every `asminject.py` payload requires, as well as the specific payload selected by the operator. It begins by notifying `asminject.py` that the instructions originally overwritten by the first stage can be restored. By doing this as early as possible, `asminject.py` reduces the likelihood of destabilizing the process if another thread attempts to call the same (overwritten) function.
Once `asminject.py` has restored the original instructions and notified the second stage, the second stage executes the operator-specified payload. After the payload has executed, it requests that `asminject.py` perform any necessary cleanup. After it receives notification that the cleanup phase is complete, the template concludes by restoring the CPU state and returning to the location where execution was originally hijacked.
## Writeable versus executable memory
In the olden days, all of a process' memory space could theoretically contain executable instructions, static data, or data that was dynamically created and updated by the process. Some software even depends on the ability to dynamically create and update its own instructions. This freedom-loving approach is referred to as allowing memory to be readable, writeable, and executable, usually abbreviated to read/write/execute, r/w/x, or `rwx`. Linux on Intel architectures still generally allows processes to map memory with these open permissions, and so `dlinject` creates a `rwx` block of memory for all of the injected code to do its work in.
However, some newer architectures and configurations use a model in which a particular block of memory can either by writeable *or* executable, but not both (sometimes abbreviated to `w^x`, for "write XOR execute"). Memory blocks containing executable instructions can't be directly modified by the process. This has benefits such as helping to prevent certain types of memory corruption attacks.
In order to work identically on both types of platform, `asminject.py` always allocates two blocks of memory: one that's readable and executable, and one that's readable and writeable. The stager and payload instructions - as well as fixed data those instructions refer to - is written to the read/execute block, and any data that needs to be updated on the fly goes in the read/write block.
## Determining which code to overwrite
## Overwriting code without crashing the target process
When injecting new code into an existing process, one typically wants to overwrite instructions that will be executed reasonably soon (so that the delay before executing the actual payload is short), but that are not currently being executed (because that would destabilize the target process).
One approach is to select a particular function that's called reasonably frequently (something like `printf`), overwrite it, and either wait for the target process to call it on its own, or do something that triggers use of that function. This is a solid approach, and `asminject.py` may eventually support it as an alternative mode, but it doesn't use it at all today because it relies on a more flexible technique.
Like its parent/sibling [dlinject](https://github.com/DavidBuchanan314/dlinject/), `asminject.py` takes advantage of another Linux pseudofile: `/proc/<pid>/syscall`. When a Linux process is waiting for a system call or similar task to complete, the `syscall` file for that process will contain the address of the instruction in its own memory space that should execute once the task finishes, as well as the stack pointer value that should be used when execution resumes at the specified instruction. In other words, if the `syscall` file contains at least two values, then `asminject.py` can discern the following information:
* The process is waiting for something else to happen
* Once that something else does happen, the CPU will begin executing instructions at the location specified in the `syscall` file
This means that `asminject.py` can wait until the `syscall` file for the target process contains those two values, then perform the following tasks:
* Back up the existing instructions starting at the instruction pointer address specified in the `syscall` file.
* Overwrite the existing instructions with the first stage.
When the task the process is waiting for completes, instead of executing the original instructions, it will automatically trigger the first stage. It doesn't matter if the process was waiting for a particular syscall or some other blocking task.
## Building the code to inject
Like [dlinject](https://github.com/DavidBuchanan314/dlinject/), the current version of `asminject.py` uses `gcc` to assemble its payloads from source immediately prior to injecting them into the target process. Unliked `dlinject`, `asminject.py` post-processes the `gcc` output using `objcopy` to extract the raw instructions from the ELF wrapper generated by `gcc`. The `objcopy` step is not strictly necessary on most x86-64 versions of Linux because that version of `gcc` supports the `--oformat=binary` option to output raw binary directly. However, `gcc` on other architectures does not seem to reliably implement that option, and I've even seen it not work as advertised on some x86-64 distributions. `dlinject` doesn't need to worry about this as much, because it only runs on x86-64 Linux. In the interest of simplifying the code, `asminject.py` uses the `objcopy` technique for all architectures even though it introduces a potentially-unnecessary step on x86-64 Linux.
A future version of `asminject.py` may support assembling code using [Keystone](https://www.keystone-engine.org/) instead of `gcc`. This would have several benefits, including the ability to perform injection without ever writing the binary code to disk.
## Injecting code without destabilizing the target process
Tampering with a running process - especially without using the standard `ptrace` debugging interface - risks destabilizing that process. This section discusses some of the ways `asminject.py` attempts to keep target processes running more or less normally during and after code injection.
### Stopping the target process
### Preserving CPU and program state
When replacing instructions in another process, it's important to avoid situations where the CPU begins executing the modified code before the modifications are complete. Consider the following simplified scenario, based on the example from the [introduction to code injection](#an-introduction-to-code-injection) section, above. The attacking process has replaced two out of three instructions, but simultaneously the CPU has already begun to execute the function. Instead of closing the file and passing the file data to the JSON function, it allocates a new block of memory and copies the second stage into that block of memory, but then instead of executing the second stage, it attempts to pass the data that would have been parsed by the JSON function to the data analysis functions:
<img src="Partially-Modified_Function-01.PNG" width="1359" height="696" alt="[ The CPU executes a partially-modified function while the attacking process is still updating it ]">
Because the JSON function was never called, there is no data to pass to the data analysis functions. At a minimum, no further data processing will take place, and the target process will therefore not behave normally. More severe effects may also occur. For example, the data analysis functions may not be written to handle a null pointer being passed, and the process could immediately crash at that point. The program might also be designed to re-read the data file if it encounters a null pointer. However, the data file was never closed, and so the re-read attempt might also result in an error or the program crashing if it can't handle attempts to read a locked file. If the program has more robust error handling, but does not limit the number of attempts to recover from the error, it may eventually exhaust all available memory, because each time it executes the `Load_Data` function, the modified instructions will cause additional memory to be allocated and never released.
If `asminject.py` were using the `ptrace` interface, it could simply send a `PTRACE_INTERRUPT` request to the target process and gracefully halt it in its tracks. As discussed in [reading and writing memory in another process](#reading-and-writing-memory-in-another-process), `asminject.py` deliberately avoids using the `ptrace` interface, so it needs to use some other method to pause the target process. It inherits several techniques from `dlinject`, but defaults to a new, potentially less intrusive approach.
I'll discuss the `dlinject`-derived methods first, since they're more straightforward.
* Any process can be paused and resumed using the OS-level `SIGSTOP` and `SIGCONT` signals. This is equivalent to using Ctrl-Z to pause and background a process in a Linux terminal session, then running the `fg` command to bring it to the foreground and resume it.
* If a process is running in a container, the process can be paused by using [the cgroup freezer interface](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)
* If process stability is not a significant concern, `asminject.py` can attempt to modify it while it's running normally. I do not recommend this approach.
I originally conceived of `asminject.py` in the context of evading security software that tried to prevent tampering with individual processes. It seemed like security software could easily flag `SIGSTOP` and `SIGCONT` signals as suspicious. Additionally, sometimes one of my test processes wouldn't *really* continue running immediately after a `SIGCONT` signal. I'd still have to bring it to the foreground from the terminal where I'd originally launched it.
I thought back to games like *The Outer Worlds*, *Horizon: Zero Dawn*, and *Superhot* that incorporate "time dilation" modes where the game world is slowed down dramatically, as well as the extreme slow motion in films like *X-Men: Days of Future Past* and *The Matrix*. What if there were a way to allow the process to continue running (i.e. not send it an explicit signal to stop), but so slowly that it would effectively be stopped relative to `asminject.py`? That might be a lot harder for monitoring software to reliably detect and counter without lots of production-breaking false positives.
Linux, like most modern operating systems, allows each process to be assigned a priority. Higher-priority processes are given preference by the CPU scheduler over lower-priority processes. So part of any "time dilation" technique would be to increase the priority of the `asminject.py` process, while decreasing the priority of the target process. But there's an additional wrinkle - most modern Linux systems also have at least two (and usually more) logical CPU cores. So even with the process priorities adjusted, the target process might still be shuffled over to an under-utilized core and continue executing. `asminject.py` therefore also temporarily assigns itself and the target process to the same core while it's performing the initial injection. This is referred to as the "CPU affinity" for each process. After injection is complete, `asminject.py` restores the original priority and CPU affinity for both itself and the target process.
The "time dilation" technique was so successful that I made it the default behaviour for `asminject.py`.
### Preserving program state
`asminject.py` backs up the instructions that will be overwritten by the first stage, and then restores them once the second stage has notified `asminject.py` that the first stage is no longer in use.
`dlinject` performs a similar backup, but the restore is handled by the assembly payload. I moved that functionality into the main script because it seemed a lot easier than writing per-architecture assembly code to do the same thing.
`dlinject` also performs special handling on the target process' stack. It backs up part of the stack before the code injection process. The second stage duplicates part of the existing stack to a location in newly-allocated memory, restores the original version of the real stack, uses the duplicate copy just long enough to perform its work, and then resets the stack pointer to the restored version of the stack from before code injection took place. This may work better in some cases, but because some `asminject.py` payloads will intentionally change the state of the program, I thought it would be better and easier to default to using the real stack throughout.
`asminject.py` includes command-line options to back up some or all additional memory regions for the target process before injection and restore them after the payload completes. They're discussed in [restoring more of the target process's memory](specialized_options.md#restoring-more-of-the-target-processs-memory).
### Preserving CPU state
Like `dlinject`, `asminject.py`'s first stage backs up as much of the CPU state as possible before any other activity, and restores those same values after the payload has finished executing.
For all supported architectures, the most important step is pushing nearly all of the CPU registers onto the stack at the beginning, and popping them back off in reverse order at the end. The main exceptions are the program counter/instruction pointer register, and the stack pointer register. Backing up and restoring those values would likely destabilize the target process.
The x86 and x86-64 architectures support some very powerful state backup/restore instructions. `asminject.py` uses the `pushfq` and `popfq` instructions on x86-64, and the `pushf`/`popf` instructions on x86 to backup and restore the state of the `EFLAGS` register. It also uses the `fxsave` and `fxrstor` instructions to backup and restore the states of the registers related to the x87 floating-point unit, MMX, and SSE/SSE2 instructions. This should result in a very complete backup/restore of the parts of the CPU state that would otherwise cause unwanted side effects after the injected code finished.
## Writeable versus executable memory
## Communicating with the target process
@@ -56,4 +157,6 @@ This view still glosses over numerous important details, but those will be cover
## Cleaning up
## Obfuscation and anti-detection
+6
View File
@@ -1,5 +1,11 @@
## asminject.py - Version history
### 0.41 (2022-10-03)
* First public release (scheduled for 2022-10-11)
* Added `--var-from-file` command-line option to make using very large variables (like entire scripts) easier
* Added `--arm-backup-fpr-1` and `--arm-backup-fpr-2` options to back up and restore ARM floating-point registers on supported architectures
### 0.40 (2022-08-05)
* Fixed a bug related to restoring the original target process priority