64位
@L_502_0@(目前为XP-64),用户空间程序如何配置“GS:”?
(通过配置,在任意64位线性地址设置GS:0).
(通过配置,在任意64位线性地址设置GS:0).
我试图将一个“JIT”环境移植到最初为Win32开发的X86-64中.
一个不幸的设计方面是,相同的代码需要在多个用户空间线程(例如,“光纤”)上运行. Win32版本的代码使用GS选择器,并生成正确的前缀来访问本地数据 – “mov eax,GS:[offset]”指向当前任务的正确数据.来自Win32版本的代码会将值加载到GS中,如果只有一个值可以工作.
到目前为止,我已经能够发现64位窗口不支持LDT,所以在Win32下使用的方法将不起作用.然而,X86-64指令集包括“SWAPGS”,以及一种在不使用传统分段的情况下加载GS的方法,但这仅适用于内核空间.
根据X64手册,即使Win64允许访问描述符 – 它没有 – 没有办法设置段32的高32位.设置它们的唯一方法是通过GS_BASE_MSR(和相应的FS_BASE_MSR – 其他段基准在64位模式下被忽略). WRMSR指令是Ring0,所以我不能直接使用它.
我希望有一个Zw *功能,可以让我改变用户空间中的“GS:”或Windows API的其他一些黑暗角落.我相信Windows仍然使用FS:为自己的TLS,所以一些机制必须可用?
此示例代码说明了该问题.我提前道歉使用字节代码 – VS将不会做64位编译的内联汇编,并且我试图将它作为一个文件保持为说明目的.
该程序在XP-32上显示“PASS”,XP-x64上不显示“PASS”.
#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,ss:[sp+4] 0x65,0x8B,0x00,gs:[eax] 0xc3 }; // ret unsigned char SetGS64[] = {0x8E,0xe9,rcx 0xC3 }; // ret unsigned char UseGS64[] = { 0x65,0x01,gs:[rcx] 0xc3 }; typedef WORD(*fcnGetDS)(void); typedef void(*fcnSetGS)(WORD); typedef DWORD(*fcnUseGS)(LPVOID); int (*NtSetLdtEntries)(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,&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); 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块中的指令基本上引发了一个软件异常. OS然后将控制权转移到修改线程上下文的过滤程序,有效地告诉操作系统跳过int3指令并继续执行.这是一个黑客,但其所有记录的功能:)