Skip to content

Instantly share code, notes, and snippets.

@merryhime
Last active June 10, 2024 08:16
Show Gist options
  • Save merryhime/f22e75d5128c07d77630ca01c4272937 to your computer and use it in GitHub Desktop.
Save merryhime/f22e75d5128c07d77630ca01c4272937 to your computer and use it in GitHub Desktop.
Playing with segment registers fs and gs on x64

GSBASE and FSBASE

When you're running out of registers while writing a JIT, you might resort to more unconventional methods for memory access. You might choose to resort to segment registers if you need a fixed register for memory offsets.

Instructions such as:

lea    rax,gs:[rcx+rdx*8]
mov    rax,gs:[rcx+rdx*8]

would then be available for your use.

This document documents what I have found about fs and gs on modern operating systems.

Linux

Doesn't care. Do what you want with gs.

#include <asm/prctl.h>
static int arch_prctl(int func, void *ptr) {
    return syscall(__NR_arch_prctl, func, ptr);
}

arch_prctl(ARCH_SET_FS, (void*)fsbase);
arch_prctl(ARCH_SET_GS, (void*)gsbase);

FreeBSD

Doesn't care.

amd64_set_fsbase((void*)fsbase);
amd64_set_gsbase((void*)gsbase);

NetBSD

Doesn't care.

sysarch(X86_64_SET_FSBASE, (void*)fsbase);
sysarch(X86_64_SET_GSBASE, (void*)gsbase);

Windows (32-bit)

The 32 bit ABI for Windows allows you to modify the equivalent of gsbase using an LDT entry.

int (*NtSetLdtEntries)(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD);
*(FARPROC*)(&NtSetLdtEntries) = GetProcAddress(LoadLibrary("ntdll.dll"), "NtSetLdtEntries");

DWORD base = /* ... */;
DWORD limit = /* ... */;

LDT_ENTRY ll;
ll.BaseLow = base & 0xFFFF;
ll.HighWord.Bytes.BaseMid = base >> 16;
ll.HighWord.Bytes.BaseHi = base >> 24;
ll.LimitLow = limit & 0xFFFF;
ll.HighWord.Bits.LimitHi = limit >> 16;
ll.HighWord.Bits.Granularity = 0;
ll.HighWord.Bits.Default_Big = 1; 
ll.HighWord.Bits.Reserved_0 = 0;
ll.HighWord.Bits.Sys = 0; 
ll.HighWord.Bits.Pres = 1;
ll.HighWord.Bits.Dpl = 3; 
ll.HighWord.Bits.Type = 0x13; 

int ret = NtSetLdtEntries(0x80, *(DWORD*)&ll, *((DWORD*)(&ll)+1), 0, 0, 0);
assert(ret >= 0);

// Then use assembly to set gs to refer to the LDT entry

Windows (64-bit)

Win 8.1 onwards enables fsgsbase instructions when they are available. This requires the application to be non-UMS.

Win 8.1 onwards explicitly allows you to use the wrgsbase instruction to directly write gsbase for user-level threading. However, the kernel checks for a valid TEB and may modify your gsbase if it doesn't point to a valid one.

Presence of this feature can be detected using IsProcessorFeaturePresent(PF_RDWRFSGSBASE_AVAILABLE).

I currently do not know if wrfsbase is available for use.

Edit: @shuffle2 has a useful post on using fs here: http://ghettohaxxx-blog.azurewebsites.net/executing-bsd-elfs-in-windows/

macOS

This looks half-promising.

That syscall is a MDEP syscall intended for the internal use of pthread to set gsbase. Looking at the mdep syscall table shows that it is the only valid mdep syscall on x64, and the rest of the code shows it does what you'd expect.

Scouering the rest of the publicly available kernel sourcecode doesn't reveal any method of setting fsbase.

If one is absolutely desperate, one could use Hypervisor.framework which would provide you with absolute control over a vCPU.

Edit 2: If you really really need to use fs for some reason, see this hack, which rewrites all fs accesses to gs accesses.

@n4sm
Copy link

n4sm commented Jul 28, 2021

Very nice, thank you so much !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment