winapi - 如何在64位Windows Assembly中使用 "GS:"(例如,移植TLS代码)

标签 winapi assembly x86-64 win64

用户空间程序如何在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/

相关文章:

Win7/AMD上的Java程序

c++ - 当控件的父窗口被销毁时,是否需要显式恢复控件字体?

c++ - 用不同的标志打开同一个文件两次?

汇编语言 : + vs add

assembly - 我可以将返回地址保存在寄存器中并将其推回堆栈中 NASM x86_64 中的 `ret` 之前吗

linux - Intel i7 (Ivy Bridge) 上的 PMU 缓存事件

c++ - 如何使用 Visual C++ Win32 API 了解 Internet 连接详细信息

c++ - 数据类型转换问题

assembly - 汇编程序如何将标签转换为地址

windows - 在 32 位 Windows 上使用 NASM 汇编创建一个 exe 文件