用户空间程序如何在 64 位 Windows(当前为 XP-64)下配置“GS:”?
(通过配置,将 GS:0 设置为任意 64 位线性地址)。
我正在尝试将“JIT”环境移植到最初为 Win32 开发的 X86-64。
一个不幸的设计方面是相同的代码需要在多个用户空间线程(例如,“光纤”)上运行。Win32 版本的代码为此使用 GS 选择器,并生成正确的前缀来访问本地数据——“mov eax,GS:[offset]”指向当前任务的正确数据。Win32 版本的代码会将一个值加载到 GS 中,只要它有一个可以工作的值。
目前发现64位windows不支持LDT,所以在Win32下用的方法不行。然而,X86-64 指令集包括“SWAPGS”,以及一种在不使用传统分段的情况下加载 GS 的方法——但这只适用于内核空间。
根据 X64 手册,即使 Win64 允许访问描述符——它不允许——也无法设置段基的高 32 位。设置这些的唯一方法是通过 GS_BASE_MSR(和相应的 FS_BASE_MSR - 其他段基在 64 位模式下被忽略)。WRMSR指令是Ring0,所以不能直接使用。
我希望有一个 Zw* 函数,它允许我在用户空间或 Windows API 的其他一些黑暗角落中更改“GS:”。我相信 Windows 仍然使用 FS: 作为自己的 TLS,所以必须有一些机制可用?
此示例代码说明了该问题。我提前为使用字节码道歉 - VS 不会为 64 位编译进行内联汇编,我试图将其保留为一个文件以用于说明目的。
该程序在 XP-32 上显示“PASS”,而在 XP-x64 上不显示。
#include <windows.h>
#include <string.h>
#include <stdio.h>
unsigned char GetDS32[] =
{0x8C,0xD8, // mov eax, ds
0xC3}; // ret
unsigned char SetGS32[] =
{0x8E,0x6C,0x24,0x04, // mov gs, ss:[sp+4]
0xC3 }; // ret
unsigned char UseGS32[] =
{ 0x8B,0x44,0x24,0x04, // mov eax, ss:[sp+4]
0x65,0x8B,0x00, // mov eax, gs:[eax]
0xc3 }; // ret
unsigned char SetGS64[] =
{0x8E,0xe9, // mov gs, rcx
0xC3 }; // ret
unsigned char UseGS64[] =
{ 0x65,0x8B,0x01, // mov eax, gs:[rcx]
0xc3 };
typedef WORD(*fcnGetDS)(void);
typedef void(*fcnSetGS)(WORD);
typedef DWORD(*fcnUseGS)(LPVOID);
int (*NtSetLdtEntries)(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD);
int main( void )
{
SYSTEM_INFO si;
GetSystemInfo(&si);
LPVOID p = VirtualAlloc(NULL, 1024, MEM_COMMIT|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);
fcnGetDS GetDS = (fcnGetDS)((LPBYTE)p+16);
fcnUseGS UseGS = (fcnUseGS)((LPBYTE)p+32);
fcnSetGS SetGS = (fcnSetGS)((LPBYTE)p+48);
*(DWORD *)p = 0x12345678;
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
{
memcpy( GetDS, &GetDS32, sizeof(GetDS32));
memcpy( UseGS, &UseGS64, sizeof(UseGS64));
memcpy( SetGS, &SetGS64, sizeof(SetGS64));
}
else
{
memcpy( GetDS, &GetDS32, sizeof(GetDS32));
memcpy( UseGS, &UseGS32, sizeof(UseGS32));
memcpy( SetGS, &SetGS32, sizeof(SetGS32));
}
SetGS(GetDS());
if (UseGS(p) != 0x12345678) exit(-1);
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
{
// The gist of the question - What is the 64-bit equivalent of the following code
}
else
{
DWORD base = (DWORD)p;
LDT_ENTRY ll;
int ret;
*(FARPROC*)(&NtSetLdtEntries) = GetProcAddress(LoadLibrary("ntdll.dll"), "NtSetLdtEntries");
ll.BaseLow = base & 0xFFFF;
ll.HighWord.Bytes.BaseMid = base >> 16;
ll.HighWord.Bytes.BaseHi = base >> 24;
ll.LimitLow = 400;
ll.HighWord.Bits.LimitHi = 0;
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;
ret = NtSetLdtEntries(0x80, *(DWORD*)&ll, *((DWORD*)(&ll)+1),0,0,0);
if (ret < 0) { exit(-1);}
SetGS(0x84);
}
if (UseGS(0) != 0x12345678) exit(-1);
printf("PASS\n");
}