用户空间程序如何在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上显示“通过”,而在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");
}
最佳答案
您可以直接通过SetThreadcontext API修改线程上下文。但是,您需要确保在更改上下文时线程未在运行。要么suspend它并从另一个线程修改上下文,要么触发伪造的SEH异常并在SEH处理程序中修改线程上下文。然后,操作系统将为您更改线程上下文并重新安排线程。
更新:
第二种方法的示例代码:
__try
{
__asm int 3 // trigger fake exception
}
__except(filter(GetExceptionCode(), GetExceptionInformation()))
{
}
int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep)
{
ep->ContextRecord->SegGs = 23;
ep->ContextRecord->Eip++;
return EXCEPTION_CONTINUE_EXECUTION;
}
try块中的指令基本上会引发软件异常。然后,操作系统将控制权转移到过滤器过程,该过程修改线程上下文,有效地告诉操作系统跳过int3指令并继续执行。
这有点像骇客,但其所有已记录的功能:)
关于winapi - 如何在64位Windows Assembly中使用 "GS:"(例如,移植TLS代码),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1180796/