Skip to content

Commit

Permalink
Added: The ability to jump beyond signed 32-bit address range via jum…
Browse files Browse the repository at this point in the history
…p relative to absolute.
  • Loading branch information
Sewer56 committed Jun 20, 2022
1 parent f880d0e commit 97caf6e
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 23 deletions.
47 changes: 47 additions & 0 deletions source/Reloaded.Hooks.Tests.Shared/IMemoryAllocator.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,66 @@
using static Reloaded.Memory.Buffers.Internal.Kernel32.Kernel32;
using System.Diagnostics;
using System;
using Reloaded.Hooks.Tools;
using Reloaded.Memory.Buffers;

namespace Reloaded.Hooks.Tests.Shared;

public interface IMemoryAllocator
{
nuint Allocate(int size);

public nuint Write(byte[] data)
{
var ptr = Allocate(data.Length);
Memory.Sources.Memory.CurrentProcess.WriteRaw(ptr, data);
return ptr;
}
}

public class ReloadedMemoryAllocator : IMemoryAllocator
{
public nuint Allocate(int size) => Memory.Sources.Memory.CurrentProcess.Allocate(size);
}

public class LowMemoryAllocator : IMemoryAllocator
{
private nuint _minAddress;
private nuint _maxAddress;
private MemoryBufferHelper _helper = new(Process.GetCurrentProcess());

public LowMemoryAllocator(nuint size = 0x10000000) // 256MB
{
_maxAddress = size + 1;
_minAddress = 1;
}

public nuint Allocate(int size)
{
var buf = Utilities.FindOrCreateBufferInRange(size, _minAddress, _maxAddress);
return buf.Add(new byte[size]);
}
}

public class HighMemoryAllocator : IMemoryAllocator
{
private nuint _minAddress;
private nuint _maxAddress;
private MemoryBufferHelper _helper = new(Process.GetCurrentProcess());

public HighMemoryAllocator(nuint size = 0x1F00000)
{
_maxAddress = MemoryAllocatorHelpers.GetMaxAddress(true);
_minAddress = (_maxAddress - size);
}

public nuint Allocate(int size)
{
var buf = Utilities.FindOrCreateBufferInRange(size, _minAddress, _maxAddress);
return buf.Add(new byte[size]);
}
}

public class MemoryAllocatorHelpers
{
/// <summary>
Expand Down
53 changes: 53 additions & 0 deletions source/Reloaded.Hooks.Tests.X64/LongJumpTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using Reloaded.Hooks.Definitions.Helpers;
using Reloaded.Hooks.Definitions.X64;
using Reloaded.Hooks.Tests.Shared;
using Reloaded.Hooks.Tools;
using Xunit;
using static Reloaded.Hooks.Definitions.X64.FunctionAttribute;
using static Reloaded.Hooks.Tests.Shared.Macros.Macros;
using static Reloaded.Memory.Sources.Memory;
using CallingConventions = Reloaded.Hooks.Definitions.X86.CallingConventions;

namespace Reloaded.Hooks.Tests.X64;

/// <summary>
/// Tests if jumps that go beyond possible limit can be replaced with long variants.
/// </summary>
public class LongJumpTest
{
private IMemoryAllocator _lowAlloc = new LowMemoryAllocator();
private IMemoryAllocator _highMemoryAllocator = new HighMemoryAllocator();

[Function(new Register[0] { }, Register.rax, false)]
[Definitions.X86.Function(CallingConventions.Cdecl)]
public delegate int GetValueFunction();

[Fact]
public void LongRelativeJump()
{
using var assembler = new Assembler.Assembler();
const int expectedResult = 42069;

string[] customFunction = new string[]
{
$"{_use32}",
$"mov {_eax}, {expectedResult}",
$"ret"
};

// Make target and source.
var target = _highMemoryAllocator.Write(assembler.Assemble(customFunction));
var src = _lowAlloc.Allocate(100); // for our jump instruction

// Assert original works.
var tgtMethod = ReloadedHooks.Instance.CreateFunction<GetValueFunction>(target.ToSigned());
Assert.Equal(expectedResult, tgtMethod.GetWrapper()());

CurrentProcess.WriteRaw(src, Utilities.AssembleRelativeJump(src, target, Environment.Is64BitProcess));

// Call the code.
var srcMethod = ReloadedHooks.Instance.CreateFunction<GetValueFunction>(src.ToSigned());
Assert.Equal(expectedResult, srcMethod.GetWrapper()());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,8 @@
<NativeLibs Remove="HookedObjectVtableTest.cs" />
</ItemGroup>

<ItemGroup>
<NativeLibs Remove="LongJumpTest.cs" />
</ItemGroup>

</Project>
19 changes: 0 additions & 19 deletions source/Reloaded.Hooks.Tests.X86/LargeAddressAwarenessTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,23 +138,4 @@ void AssertLargeAddressAware()
Assert.False(true, "Test host is not large address aware!!");
}
}

public class HighMemoryAllocator : IMemoryAllocator
{
private nuint _minAddress;
private nuint _maxAddress;
private MemoryBufferHelper _helper = new(Process.GetCurrentProcess());

public HighMemoryAllocator()
{
_maxAddress = MemoryAllocatorHelpers.GetMaxAddress(true);
_minAddress = (_maxAddress - 0x1F00000); // ~32 MB. hopefully OS wouldn't complain.
}

public nuint Allocate(int size)
{
var buf = Utilities.FindOrCreateBufferInRange(size, _minAddress, _maxAddress);
return buf.Add(new byte[size]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<Compile Include="..\Reloaded.Hooks.Tests.X64\FastcallCalculatorTest.cs" Link="FastcallCalculatorTest.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\FunctionPatcherTest.cs" Link="FunctionPatcherTest.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\HookedObjectVtableTest.cs" Link="HookedObjectVtableTest.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\LongJumpTest.cs" Link="LongJumpTest.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\ReloadedHooksTest.cs" Link="ReloadedHooksTest.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\SuperStackedHooks.cs" Link="SuperStackedHooks.cs" />
<Compile Include="..\Reloaded.Hooks.Tests.X64\VTableTest.cs" Link="VTableTest.cs" />
Expand Down
32 changes: 28 additions & 4 deletions source/Reloaded.Hooks/Tools/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,37 @@ public static unsafe nuint WritePointer(nuint target)
/// <param name="is64bit">True to generate x64 code, else false (x86 code).</param>
public static byte[] AssembleRelativeJump(nuint currentAddress, nuint targetAddress, bool is64bit)
{
long offset = (long)targetAddress - (long)currentAddress;
if (Math.Abs(offset) <= Int32.MaxValue)
{
return Assembler.Assemble(new[]
{
Architecture(is64bit),
SetAddress(currentAddress),
is64bit ? $"jmp qword {targetAddress}" : $"jmp dword {targetAddress}"
});
}

// Hack: Work around invalid jumps.
// There are legitimate possibilities of edge cases whereby it may not be possible to
// jump from source to target, such as when there isn't sufficient memory.
// We're going to try hack past this with a simple hack for now, it's not perfect but
// it should be good enough in the meantime.

// Note: This code only handles signed cases in 64-bit due to length of long.
// but given the address space of 64b, I don't consider this to be a limitation in my lifetime.

// If we are exceeding the max jump range, try to
// find a buffer within the range of currentaddress and
// jump to it, then absolute jump from that one.
var minMax = GetRelativeJumpMinMax(currentAddress);
var buffer = FindOrCreateBufferInRange(16, minMax.min, minMax.max); // No code alignment as this is edge case.
var absoluteJumpAddress = buffer.Add(AssembleAbsoluteJump(targetAddress, is64bit));
return Assembler.Assemble(new[]
{
Architecture(is64bit),
SetAddress(currentAddress),
is64bit ? $"jmp qword {targetAddress}" : $"jmp dword {targetAddress}"
is64bit ? $"jmp qword {absoluteJumpAddress}" : $"jmp dword {absoluteJumpAddress}"
});
}

Expand Down Expand Up @@ -206,9 +232,7 @@ public static nuint CreateJump(nuint targetPtr, bool is64Bit, int extraBytes = 0
int maxFunctionSize = 64 + extraBytes;
var minMax = Utilities.GetRelativeJumpMinMax(targetPtr, Int32.MaxValue - maxFunctionSize);
var buffer = Utilities.FindOrCreateBufferInRange(maxFunctionSize, minMax.min, minMax.max);

// If


return buffer.ExecuteWithLock(() =>
{
// Align the code.
Expand Down

0 comments on commit 97caf6e

Please sign in to comment.