Skip to content

Commit

Permalink
Added: 128-bit atomics & completed assembly hook/unhook code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sewer56 committed Dec 21, 2023
1 parent 32a22ff commit 94f9048
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 32 deletions.
7 changes: 3 additions & 4 deletions docs/dev/design/assembly-hooks/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,6 @@ The following table below shows common hook lengths, for:

## Thread Safety & Memory Layout

!!! note "[Reloaded3](https://reloaded-project.github.io/Reloaded-III/) allows mod load/unloads in real time, so this is a hard requirement."

!!! warning "Therefore, assembly hooks should be thread safe."

In order to support thread safety, while retaining maximum runtime performance, the buffers where the
original and hook code are contained have a very specific memory layout (shown below)

Expand All @@ -149,6 +145,9 @@ original and hook code are contained have a very specific memory layout (shown b
- Original Code
```

Emplacing the jump to the hook function itself, and patching within the hook function should be atomic
whenever it is possible on the platform.

### Example

If the *'Original Code'* was:
Expand Down
93 changes: 92 additions & 1 deletion docs/dev/design/branch-hooks/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

!!! warning "Only guaranteed to work on platforms with [Targeted Memory Allocation](../../platform/overview.md#recommended-targeted-memory-allocation)"

Because the library needs to be able to acquire memory in proximity of the original function.

Usually this is almost always achievable, but cases where Denuvo DRM inflates ARM64 binaries
(20MB -> 500MB) may prove problematic as ARM64 has +-128MiB range for relative jumps.

!!! note "I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional."

This hook works by replacing the target of a `call` (a.k.a. Branch with Link) instruction with a new target.
Expand Down Expand Up @@ -85,4 +90,90 @@ flowchart TD
When the hook is deactivated, the stub is replaced with a direct jump back to the original function.

By bypassing your code entirely, it is safe for your dynamic library (`.dll`/`.so`/`.dylib`)
to unload from the process.
to unload from the process.

## Example

### Before

```asm
; x86 Assembly
originalCaller:
; Some code...
call originalFunction
; More code...
originalFunction:
; Function implementation...
```

### After (Fast Mode)

```asm
; x86 Assembly
originalCaller:
; Some code...
call newFunction
; More code...
newFunction:
; New function implementation...
call originalFunction ; Optional.
originalFunction:
; Original function implementation...
```

### After

```asm
; x86 Assembly
originalCaller:
; Some code...
call stub
; More code...
stub:
jmp newFunction
; nop padding to 8 bytes (if needed)
newFunction:
; New function implementation...
call originalFunction ; Optional.
originalFunction:
; Original function implementation...
```

### After (with Calling Convention Conversion)

```asm
; x86 Assembly
originalCaller:
; Some code...
call wrapper
; More code...
wrapper:
; call convention conversion implementation
call newFunction
; call convention conversion implementation
ret
newFunction:
; New function implementation...
call reverseWrapper ; Optional.
reverseWrapper:
; call convention conversion implementation
call originalFunction
; call convention conversion implementation
ret
originalFunction:
; Original function implementation...
```

## Thread Safety & Memory Layout

Emplacing the jump to the stub and patching within the stub are atomic operations on all supported platforms.
1 change: 1 addition & 0 deletions projects/reloaded-hooks-portable/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bitflags = "2.4.1"
derive_more = { version = "0.99.17", default-features = false, features = ["from", "add", "iterator"] }
derive-new = { version = "0.6.0", default-features = false }
bitfield = "0.14.0"
portable-atomic = "1.6.0"

# Tests only!
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ pub trait Buffer {
/// This method works around the complicated tidbits of writing to buffer, such as instruction
/// cache invalidation and permission changes on W^X systems where applicable.
///
/// `TInteger` must be a native integer type, such as `u8`, `u16`, `u32`, `u64`, which can be written
/// using a single instruction.
/// `TInteger` must be a native integer type, such as `u8`, `u16`, `u32`, `u64`, `u128` which
/// can be written using a single instruction.
///
/// # Parameters
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,18 @@ where
/// Writes the hook to memory, either enabling or disabling it based on the provided parameters.
fn write_hook(&self, branch_opcode: &[u8], code: &[u8], num_bytes: usize) {
// Write the branch first, as per docs
TBuffer::overwrite(self.stub_address, branch_opcode);
// This also overwrites some extra code afterwards, but that's a-ok for now.
unsafe {
atomic_write_masked::<TBuffer>(self.stub_address, branch_opcode, num_bytes);
}

// Now write the remaining code
TBuffer::overwrite(self.stub_address + num_bytes, &code[num_bytes..]);

// Now write the non-branch code
atomic_write_masked::<TBuffer>(self.stub_address, &code[..num_bytes], num_bytes);
// And now re-insert the code we temp overwrote with the branch
unsafe {
atomic_write_masked::<TBuffer>(self.stub_address, code, num_bytes);
}
}

/// Enables the hook.
Expand Down
8 changes: 7 additions & 1 deletion projects/reloaded-hooks-portable/src/helpers/atomic_write.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, Ordering};

use portable_atomic::AtomicU128;

/// Performs an atomic write of value in `src` to `tgt`.
/// Size must be 1/2/4/8 bytes.
/// Size must be 1/2/4/8/16 bytes.
///
/// # Safety
///
Expand All @@ -25,6 +27,10 @@ pub unsafe fn atomic_write(src: *const u8, tgt: *mut u8, size: usize) {
let atomic = (tgt as *mut AtomicU64).as_ref().unwrap_unchecked();
atomic.store(*(src as *const u64), Ordering::Relaxed);
}
16 => {
let atomic = (tgt as *mut AtomicU128).as_ref().unwrap_unchecked();
atomic.store(*(src as *const u128), Ordering::Relaxed);
}
_ => panic!("Unsupported size for atomic write."),
}
}
Loading

0 comments on commit 94f9048

Please sign in to comment.