Skip to content

Instantly share code, notes, and snippets.

@merryhime
Last active April 11, 2025 01: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.

macOS

Fuck you.

@n4sm
Copy link

n4sm commented Jul 28, 2021

Very nice, thank you so much !

@CensoredUsername
Copy link

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

Note: lea seems to ignore any segment registers used in the instruction for its address calculation.

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