darfink / detour-rs

A cross-platform detour library written in Rust
Other
406 stars 75 forks source link

Improve cross-platform support for platforms with greater r^x restrictions #28

Open jam1garner opened 2 years ago

jam1garner commented 2 years ago

Motivation

Currently, this library uses a few abstractions which assume rwx abstractions exist. This model, however, is becoming increasingly less popular in embedded devices, with iOS and the Nintendo Switch both using doubly mapped memory for JIT, (one being rw- and another being either r-x or --x) rather than remapping existing memory, as this reduces the attack surface for ROP to escalate into arbitrary code execution. This approach is also becoming popular in other systems (another example being .NET 6 using this to harden their JIT against escalation of vulnerabilities)

In order to support more targets, it'd likely be better to abstract away the actual read/write accesses behind a Deref<Target = [u8]> implementation in order to allow target-specific access patterns.

My personal motivation here is possibly working with @Raytwo to add aarch64 support and hopefully eventually make use of it for providing a unified library for modding games on both PC and the Nintendo Switch. Since the Switch doesn't support rwx and has no direct equivalent to mmap*, these changes are necessary in order to make the library workable for our usecase.

*sorta

Implementation Proposal

From reading through the source I'm primarily seeing 2 things which rely on this assumption:

  1. region::protect_with_handle - Being used to rwx map existing r-x memory temporarily in order to enable patching existing code
    • Rework to use a function for getting a RAII guard for accessing the memory as rw-. The RwGuard<'a> would implement Deref<Target = [u8]> in order to deref to the rw- mapping for the memory. For platforms continuing to use the region library, this would simply Deref to the rwx memory, for other platforms it would Deref to the second rw- mapping which would have different addresses but maps to the same memory. This guard would also implement Drop to handle any locking/unlocking/icache clearing that would need to be done.
      
      fn get_as_rw<'a, T>(addr: *const T, size: usize) -> RwGuard<'a>;

impl<'a> Deref for RwGuard<'a> { type Target = [u8]; }

impl<'a> DerefMut for RwGuard<'a> {}

2. `mmap` - Being used to rwx map entirely new memory to a specific address
    * This one is easier, similar approach to the last one (return a struct which implements `Deref<Target=[u8]>`) but with less of a RAII guard style usage and more just "this is now a mapping that is owned by this type".
```rust
impl RwxMap {
    fn new(addr: *const T, size: usize) -> Result<Self, Error>;
    fn addr(&self) -> *const c_void;
    // ...
}